2026-05-08 18:43:45 +00:00
2026-05-08 18:43:45 +00:00

reactbin

Organize your reaction images.

Reactbin UI

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

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-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

# 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):
    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:
    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
Organize your reaction images.
Readme 2.9 MiB
Languages
Python 32.7%
TypeScript 32.1%
Shell 26.6%
PowerShell 6.9%
JavaScript 0.5%
Other 1.2%