Docs: Add comprehensive README with local dev and production deployment guide
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
138
README.md
138
README.md
@@ -2,3 +2,141 @@
|
|||||||
_Organize your reaction images._
|
_Organize your reaction images._
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
A self-hosted reaction image board. Single owner account, tag-based browsing, S3-compatible image storage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env — defaults work out of the box for local dev
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
- UI: http://localhost:4200
|
||||||
|
- API: http://localhost:8000
|
||||||
|
- MinIO console: http://localhost:9001 (minioadmin / minioadmin)
|
||||||
|
|
||||||
|
The API serves on port 8000 directly in dev. In production the nginx ingress routes `/api/` there.
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test-unit # pytest unit tests (no Docker)
|
||||||
|
make test-integration # builds api-test image, runs full suite against Postgres + MinIO
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production image builds
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build-prod # builds reactbin-api-prod:latest from api/Dockerfile.prod
|
||||||
|
make verify-prod # smoke-tests the production image
|
||||||
|
make build-ui-prod # builds reactbin-ui-prod:latest from ui/Dockerfile.prod
|
||||||
|
make verify-ui-prod # smoke-tests the production UI image
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production deployment (k3s)
|
||||||
|
|
||||||
|
### Cluster prerequisites
|
||||||
|
|
||||||
|
- nginx ingress controller
|
||||||
|
- cert-manager with a `letsencrypt-prod` ClusterIssuer
|
||||||
|
- Vault Secrets Operator (VSO) installed and connected to Vault
|
||||||
|
- Vault KV v2 secrets populated (see below)
|
||||||
|
|
||||||
|
### Vault secrets
|
||||||
|
|
||||||
|
Two KV v2 paths. VSO syncs these into Kubernetes Secrets automatically.
|
||||||
|
|
||||||
|
**`reactbin/api/config`** → K8s Secret `api-env`
|
||||||
|
|
||||||
|
| Key | Notes |
|
||||||
|
|-----|-------|
|
||||||
|
| `DATABASE_URL` | `postgresql+asyncpg://user:pass@host:5432/db` |
|
||||||
|
| `JWT_SECRET_KEY` | Long random string — `openssl rand -base64 48` |
|
||||||
|
| `OWNER_USERNAME` | Login username |
|
||||||
|
| `OWNER_PASSWORD` | Login password |
|
||||||
|
| `S3_ENDPOINT_URL` | `http://minio.reactbin.svc.cluster.local:9000` |
|
||||||
|
| `S3_BUCKET_NAME` | `reactbin` |
|
||||||
|
| `S3_ACCESS_KEY_ID` | Same value as `MINIO_ROOT_USER` |
|
||||||
|
| `S3_SECRET_ACCESS_KEY` | Same value as `MINIO_ROOT_PASSWORD` |
|
||||||
|
| `API_BASE_URL` | `https://<your-domain>` |
|
||||||
|
| `LOGIN_TRUSTED_PROXY_IPS` | Pod CIDR of nginx ingress pods, e.g. `10.42.0.0/16` — needed for per-client login rate limiting behind the ingress |
|
||||||
|
|
||||||
|
**`reactbin/minio/credentials`** → K8s Secret `minio-credentials`
|
||||||
|
|
||||||
|
| Key | Notes |
|
||||||
|
|-----|-------|
|
||||||
|
| `MINIO_ROOT_USER` | MinIO admin username |
|
||||||
|
| `MINIO_ROOT_PASSWORD` | `openssl rand -base64 32` |
|
||||||
|
|
||||||
|
### Apply order
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Namespace first
|
||||||
|
kubectl apply -f k8s/namespace.yaml
|
||||||
|
|
||||||
|
# 2. Vault CRDs — wait for VSO to create api-env and minio-credentials Secrets
|
||||||
|
kubectl apply -f k8s/vault/
|
||||||
|
kubectl get secret -n reactbin api-env minio-credentials # wait until both appear
|
||||||
|
|
||||||
|
# 3. API, UI, Ingress — replace 'latest' tags and <your-domain> first
|
||||||
|
kubectl apply -f k8s/api/ -f k8s/ui/ -f k8s/ingress.yaml
|
||||||
|
kubectl rollout status deployment/api -n reactbin # Alembic init container runs here
|
||||||
|
|
||||||
|
# 4. MinIO — wait for StatefulSet ready before running the bucket init Job
|
||||||
|
kubectl apply -f k8s/minio/service.yaml -f k8s/minio/statefulset.yaml
|
||||||
|
kubectl rollout status statefulset/minio -n reactbin
|
||||||
|
kubectl apply -f k8s/minio/init-job.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Before applying: substitute real image tags in the Deployment manifests and replace `<your-domain>` in `k8s/ingress.yaml`.
|
||||||
|
|
||||||
|
### Updating a secret
|
||||||
|
|
||||||
|
1. Update the value in Vault
|
||||||
|
2. Force VSO to sync immediately (otherwise waits up to 1 hour):
|
||||||
|
```bash
|
||||||
|
kubectl annotate vaultstaticsecret api-secret -n reactbin \
|
||||||
|
secrets.hashicorp.com/force-sync=$(date +%s) --overwrite
|
||||||
|
```
|
||||||
|
3. Restart the deployment to pick up the new Secret:
|
||||||
|
```bash
|
||||||
|
kubectl rollout restart deployment/api -n reactbin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validating manifests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make validate-k8s # yamllint + kubectl apply --dry-run=client (requires kubeconfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment variables reference
|
||||||
|
|
||||||
|
All variables are read at startup from environment / `.env`.
|
||||||
|
|
||||||
|
| Variable | Default | Notes |
|
||||||
|
|----------|---------|-------|
|
||||||
|
| `DATABASE_URL` | — | Async DSN: `postgresql+asyncpg://...` |
|
||||||
|
| `JWT_SECRET_KEY` | — | Required; use a long random string in production |
|
||||||
|
| `JWT_EXPIRY_SECONDS` | `86400` | Token lifetime (24 h) |
|
||||||
|
| `OWNER_USERNAME` | — | Single owner account username |
|
||||||
|
| `OWNER_PASSWORD` | — | Single owner account password |
|
||||||
|
| `S3_ENDPOINT_URL` | — | MinIO or any S3-compatible endpoint |
|
||||||
|
| `S3_BUCKET_NAME` | `reactbin` | |
|
||||||
|
| `S3_ACCESS_KEY_ID` | — | |
|
||||||
|
| `S3_SECRET_ACCESS_KEY` | — | |
|
||||||
|
| `S3_REGION` | `us-east-1` | |
|
||||||
|
| `MAX_UPLOAD_BYTES` | `52428800` | 50 MiB |
|
||||||
|
| `API_BASE_URL` | — | Used for generating public URLs |
|
||||||
|
| `API_DOCS_ENABLED` | `true` | Set to `false` in production |
|
||||||
|
| `LOGIN_MAX_FAILURES` | `5` | Failed attempts before cooldown |
|
||||||
|
| `LOGIN_WINDOW_SECONDS` | `300` | Sliding window for failure count |
|
||||||
|
| `LOGIN_COOLDOWN_SECONDS` | `900` | Lock duration after threshold hit |
|
||||||
|
| `LOGIN_TRUSTED_PROXY_IPS` | `""` | Comma-separated CIDRs of trusted upstream proxies |
|
||||||
|
|||||||
Reference in New Issue
Block a user