agatha e4a77fdea3 CI: Move Postgres to manual docker run with shared network namespace
Service containers bind ports to the host, not to localhost inside the
job container. Start both Postgres and MinIO manually with
--network container:$(hostname) so they share the job container's
network namespace and are reachable on localhost. Use docker exec for
pg_isready to avoid depending on postgresql-client in the runner image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:52:14 +00:00
2026-05-10 18:15:10 +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%