aaacfae65343ea2f80d94fd56bc73a611b4bb4ca
API responses now include file_url and thumbnail_url fields. When S3_PUBLIC_BASE_URL is configured, these point to the CDN domain; when unset, they fall back to the existing API proxy paths so local dev requires no additional setup. UI updated to use response URL fields directly instead of constructing proxy URLs client-side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
reactbin
Organize your reaction images.
A self-hosted reaction image board. Single owner account, tag-based browsing, S3-compatible image storage.
Local development
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
make test-unit # pytest unit tests (no Docker)
make test-integration # builds api-test image, runs full suite against Postgres + MinIO
Production image builds
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-prodClusterIssuer - 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
# 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
- Update the value in Vault
- Force VSO to sync immediately (otherwise waits up to 1 hour):
kubectl annotate vaultstaticsecret api-secret -n reactbin \ secrets.hashicorp.com/force-sync=$(date +%s) --overwrite - Restart the deployment to pick up the new Secret:
kubectl rollout restart deployment/api -n reactbin
Validating manifests
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 |
Description
Languages
Python
32.7%
TypeScript
32.1%
Shell
26.6%
PowerShell
6.9%
JavaScript
0.5%
Other
1.2%
