https://rxresu.me, and your data stays on
your hardware.
Before you start
You need:- A Linux, macOS or Windows host with Docker Engine and the Docker Compose plugin (or Docker Desktop).
- At least 1 vCPU and 1 GB of memory for the app. Allow 2 GB when PostgreSQL runs on the same host.
- Disk space for the database and uploaded pictures. 10 GB is plenty to start.
- For anything beyond a test on your own machine: a domain name and a reverse proxy that serves it over HTTPS. See Deployment examples for Traefik, Caddy and nginx.
How the pieces fit
The official image runs a single Node.js process on port3000. It serves the web app, the API, the MCP server and
uploaded files from one address. PDFs are rendered by the app itself, in the browser or on the server, so there is no
separate printer service.
- Docker Hub:
amruthpillai/reactive-resume - GitHub Container Registry:
ghcr.io/reactive-resume/reactive-resume
linux/amd64 and linux/arm64, so they run on most servers, Apple silicon, and a Raspberry
Pi 4 or newer running a 64-bit operating system.
Set up with Docker Compose
Create a folder for your instance
.env for settings and compose.yml for the containers.Create the .env file
.env with the three required settings:AUTH_SECRET and a database password, and paste them in. Use the same password in
POSTGRES_PASSWORD and DATABASE_URL.APP_URL to that address now, for example
https://resume.example.com. Every other setting is optional; the
environment variable reference lists them all.Create compose.yml
reactive_resume_data volume, so they survive when the container is recreated. The image has a built-in health
check, so Compose reports the app as healthy once it’s ready.Start the containers
--wait option waits for PostgreSQL and the app to
become healthy, with a timeout of 180 seconds. When it succeeds, open your APP_URL (in this example,
http://localhost:3000).If startup fails or times out, check the service status and recent app logs:Create your account
APP_URL in a browser and create an account; see Creating an account.You’re signed in right away. Reactive Resume also sends a verification email, but you don’t have to open it to
use the app. Without SMTP settings the email isn’t sent; its link is written to the app log instead:- Keep it private. After your own account exists, add
FLAG_DISABLE_SIGNUPS="true"to.envand rundocker compose up -d. Nobody else can sign up; existing accounts keep working. - Send real emails. Add the
SMTP_*settings from the reference. - Turn on job search and AI. Connect shared Firecrawl and AI services, or enable personal keys with
ENCRYPTION_SECRET. See Job search and AI. - Serve it over HTTPS. Put a reverse proxy in front and set
APP_URLto the publichttps://address. See Deployment examples.
What happens at startup
Each time the app container starts, it:- Applies any new database migrations. Several containers starting at once take turns, so only one migrates.
- Compares the database schema with what the migrations expect. A mismatch is logged; set
STRICT_SCHEMA_CHECK=trueto stop instead. - With local storage, checks that
/app/data(orLOCAL_STORAGE_PATH) is writable, and stops if it isn’t. - Starts serving on
PORT.
DATABASE_URL, a
database that isn’t reachable yet, or a storage folder the container can’t write to.
Check the health endpoint
GET /api/health reports whether the app can reach its database, storage and, when configured, Redis. It answers
200 when everything is healthy and 503 when any check fails. The image’s built-in health check calls it every 30
seconds.
redis field appears only when REDIS_URL is set. When a check fails, its entry shows "status": "unhealthy"
and the app log has the details. Each check times out after 1.5 seconds.
Use this endpoint for load balancer or orchestrator readiness checks. Restarting the app doesn’t fix a database or
storage outage, so avoid using it to kill containers.
Update your instance
Back up first
Pull the new image
bash docker compose pull reactive-resume Recreate the app container
Back up your data
Your data lives in two places. Back up both, on a schedule, and test that you can restore them.-
Database. Dump it with
pg_dump:Restore into an empty database withpg_restore: -
Uploads. With local storage, copy the contents of the
reactive_resume_datavolume (or your bind-mounted folder). With S3, turn on bucket versioning or replication in your provider.
.env too. Without the same AUTH_SECRET and ENCRYPTION_SECRET, a restored instance signs
everyone out, breaks two-factor authentication and loses saved AI and Firecrawl keys.
Build from the repository
The repository’s owncompose.yml builds the image from source and also starts Redis and SeaweedFS (S3-compatible
storage). It reads .env.example first and then your .env.
Unraid, Synology and other homelab platforms
There is no official app template. Use your platform’s generic container settings:- Create two containers: one from
amruthpillai/reactive-resume:latestand one frompostgres:18. Give PostgreSQL its own persistent volume at/var/lib/postgresql. - Put both containers on the same private network, and use the PostgreSQL container’s name as the host in
DATABASE_URL. - Map the app’s container port
3000to any free host port. - Map a persistent folder to
/app/data. The app runs as thenodeuser (UID 1000), so that user must be able to write to it. - Set
APP_URL,DATABASE_URLandAUTH_SECRETas environment variables.
localhost means the app container itself. It never reaches a database in another
container, so don’t use localhost in DATABASE_URL.Troubleshooting
The app container keeps restarting
The app container keeps restarting
docker compose logs reactive-resume. The last lines name the problem:Invalid environment variables: a required variable is missing or malformed. The message names it.Database migrations failedorECONNREFUSED:DATABASE_URLis wrong or PostgreSQL isn’t reachable. Check the host name, password (URL-encode special characters) and that PostgreSQL is healthy.Local storage path is not writable: the folder mounted at/app/dataisn’t writable by UID 1000. Fix its owner, or use a named volume.
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 you’re using, or you’re on https:// while APP_URL says http://. Set
APP_URL to the exact public address and run docker compose up -d.Verification or password reset emails never arrive
Verification or password reset emails never arrive
SMTP_HOST, SMTP_USER, SMTP_PASS and SMTP_FROM are all set. Until then they’re
written to the app log. If they’re set, check SMTP_PORT and SMTP_SECURE against your provider’s settings and look
for SMTP errors in the log./api/health returns 503
/api/health returns 503
unhealthy. For storage, check the /app/data mount or your S3 settings. For redis,
check that Redis is running and REDIS_URL is correct.Uploaded pictures disappear after an update
Uploaded pictures disappear after an update
/app/data, so uploads lived inside the old container. Mount a volume there as in
the example above. Pictures uploaded before the change are gone.S3 error: getaddrinfo ENOTFOUND bucket.endpoint
S3 error: getaddrinfo ENOTFOUND bucket.endpoint
S3_FORCE_PATH_STYLE="true".The Assistant says it isn't set up on this server
The Assistant says it isn't set up on this server
ENCRYPTION_SECRET to at least 32 characters so users can save personal
providers. Recreate the container, then check Job search and AI.PDF download fails in the browser
PDF download fails in the browser
'wasm-unsafe-eval' in script-src. Also check for browser extensions that block downloads.Related pages
- Environment variables: every setting, with defaults.
- Deployment examples: reverse proxies, S3 storage, Redis and local AI.
- Job search and AI: Firecrawl, SearXNG and shared or personal AI providers.
- Self-hosting with Kubernetes: the same setup as Kubernetes manifests.
- Self-hosting on Hostinger: a 1-click deploy of the same stack on a Hostinger VPS.
- Checking service status: the status of the hosted instance.