3000, a separate PostgreSQL
database, and persistent storage for uploads. Adapt the storage class and Ingress settings to your cluster.
Before you start
You need:- A cluster with
kubectlaccess and a default StorageClass for PersistentVolumeClaims. - An Ingress controller and a way to issue TLS certificates, such as cert-manager.
- A DNS name for your instance, for example
resume.example.com. - About 250m CPU and 512 MiB of memory for the app Pod, plus room for PostgreSQL if it runs in the cluster.
amruthpillai/reactive-resume on Docker Hub or ghcr.io/reactive-resume/reactive-resume on GitHub
Container Registry, built for amd64 and arm64. The examples pin the v6 tag, which gets fixes and new features
without moving to the next major version.
Create the namespace and Secret
Save both manifests. The Secret holds every setting and is passed to the app as environment variables.namespace.yaml
secret.yaml
openssl rand -hex 32, and paste them in. See
Environment variables for everything else you can add, such as SMTP, S3 and
single sign-on.
Run PostgreSQL
You can use a managed database, an operator such as CloudNativePG, or this minimal single-instance Deployment. If you use your own, skip this file and setDATABASE_URL to it.
postgres.yaml
- Keep this Service a ClusterIP. Don’t expose PostgreSQL outside the cluster.
POSTGRES_PASSWORDonly applies when the database is first created. Changing it later doesn’t change the password.- Inside the cluster, use the Service name as the host in
DATABASE_URL, neverlocalhost.
Deploy the app
This file adds the uploads volume, the Deployment, a Service and an Ingress.reactive-resume.yaml
resume.example.com, ingressClassName and the cluster-issuer with your own values, and point your DNS at
the Ingress controller. The annotations let the Ingress accept Assistant attachments of up to 25 MB and stream replies
that take up to 4 minutes; What every reverse proxy needs
explains why. That section also covers client IP headers: make sure your controller doesn’t pass on
CF-Connecting-IP, CF-Connecting-IPv6 or True-Client-IP headers sent by clients, or they can get around per-IP
rate limits.
Apply and verify
1
Apply the manifests
Ready once its startup migrations finish and /api/health answers 200. Follow along with
kubectl -n reactive-resume logs -f deployment/reactive-resume.2
Create your account
Open your
APP_URL and create an account. You’re signed in right away. Without SMTP, the verification email is
written to the app log instead of being sent.3
Check that data persists
Create a resume from a sample and upload a profile picture. Then replace the Pod:Reload the page. The resume and picture should still be there. Expect a short outage while a single-replica
Deployment restarts.
4
Close sign-ups (optional)
For a private instance, add
FLAG_DISABLE_SIGNUPS: "true" to secret.yaml after your account exists, apply it,
and restart the Deployment. Pods only read Secret changes when they start.How the probes work
- The startup probe gives the first start up to 5 minutes to run migrations before other probes begin.
- The readiness probe calls
/api/health, which checks the database, storage and, when configured, Redis. It answers503if any of them fails, and Kubernetes stops sending traffic to that Pod until it recovers. - There is deliberately no liveness probe on
/api/health. Restarting the app doesn’t fix a database or storage outage. If the server process itself exits, Kubernetes restarts the container anyway.
Run several replicas
The example runs one replica because uploads live on aReadWriteOnce volume. To run more:
- Switch uploads to S3-compatible storage: add the
S3_*variables to the Secret, then remove the PVC, the volume and the/app/datamount. See Store uploads in S3-compatible storage. - Add Redis and set
REDIS_URLin the Secret, so rate limits, stopping Assistant replies and live updates work across Pods. - Change
strategytoRollingUpdateand raisereplicas.
Update the app
- Back up the database and uploads.
-
Restart the Deployment to pull the newest
v6image: - Check the logs while the new version applies its migrations.
v6.0.0 (or an image digest), change it in
reactive-resume.yaml, and run kubectl apply -f reactive-resume.yaml. Coming from v5? Read
Migrating from v5 first.
Back up PostgreSQL and the uploads (the PVC or the S3 bucket) on a schedule, and keep a copy of the Secret. Update
PostgreSQL separately from the app, following your operator’s or provider’s upgrade process.
Community Helm chart
A community-maintained Helm chart is available from HelmForge:- Chart source: helmforgedev/charts
- Documentation: helmforge.dev
Troubleshooting
The app Pod is in CrashLoopBackOff
The app Pod is in CrashLoopBackOff
Read the logs with
kubectl -n reactive-resume logs deployment/reactive-resume --previous. Invalid environment variables names a missing or malformed setting. Database migrations failed usually means a wrong
DATABASE_URL or PostgreSQL isn’t ready. Local storage path is not writable means the volume isn’t writable by
UID 1000; keep fsGroup: 1000 or fix the volume’s permissions.The Pod never becomes Ready
The Pod never becomes Ready
Port-forward and call
/api/health. The entry marked unhealthy points at the problem: the database, the uploads
volume or S3 settings, or Redis.Sign-in loops back to the sign-in page
Sign-in loops back to the sign-in page
APP_URL doesn’t match the address in the browser, or it says http:// while you use https://. Fix it in the
Secret, apply, and restart the Deployment.Assistant replies stop partway or attachments fail to upload
Assistant replies stop partway or attachments fail to upload
The Ingress is buffering responses, timing out after 60 seconds, or rejecting large bodies. Apply the annotations
from the example, or your controller’s equivalents.
Related pages
- Environment variables: every setting you can put in the Secret.
- Self-hosting with Docker: the same setup with Docker Compose, plus backups.
- Deployment examples: S3 storage, Redis, local AI and private instances.