* chore(release): v5.1.0

* feat: implement resume thumbnails

* fix: remove unused mcp tools

* docs: fix formatting of docs
This commit is contained in:
Amruth Pillai
2026-05-07 15:12:33 +02:00
committed by GitHub
parent 51c366310e
commit 50ba37a27f
1015 changed files with 106087 additions and 141872 deletions
+16 -82
View File
@@ -1,15 +1,18 @@
---
title: "Self-Hosting with Docker"
description: "A comprehensive guide to self-host Reactive Resume with Docker (Postgres + Printer), including a detailed environment variable reference and troubleshooting tips."
description: "A comprehensive guide to self-host Reactive Resume with Docker (Postgres only), including a detailed environment variable reference and troubleshooting tips."
---
<Info>
**From v5.1.0 onwards** — PDF generation now runs entirely client-side via `@react-pdf/renderer`. New deployments no longer require Browserless, Chromium, or any external print service as a dependency. The `PRINTER_*` and `BROWSERLESS_*` environment variables are no longer read and can be removed from your `.env`.
</Info>
## Overview
Reactive Resume can be self-hosted using Docker in a matter of minutes, and this guide will walk you through the process. Here are some of the services you'll need to get started:
<CardGroup cols={2}>
<Card title="PostgreSQL">Stores accounts, resumes, and application data.</Card>
<Card title="Printer">Generates PDFs and screenshots using a headless Chromium browser.</Card>
<Card title="Email (optional)">
SMTP for verification emails, password reset, etc. If not configured, emails are logged to the server console.
</Card>
@@ -27,7 +30,7 @@ You can pull the latest app image from:
<CardGroup cols={1}>
<Card title="Docker + Docker Compose">Docker Engine + Docker Compose plugin (or Docker Desktop).</Card>
<Card title="Compute">2 vCPU / 2 GB RAM minimum (4 GB recommended if Postgres + Printer run on the same host).</Card>
<Card title="Compute">1 vCPU / 1 GB RAM minimum (2 GB recommended if Postgres runs on the same host).</Card>
<Card title="Storage">Enough for Postgres + uploads (start with 10-20 GB and scale as needed).</Card>
</CardGroup>
@@ -48,16 +51,6 @@ Create a new folder (for example `reactive-resume/`) with:
TZ="Etc/UTC"
APP_URL="http://localhost:3000"
# Optional, uses APP_URL by default
# This can be set to a different URL (like http://host.docker.internal:3000 or http://{docker_service}:3000)
# to let the browser navigate to a non-public instance of Reactive Resume
PRINTER_APP_URL="http://host.docker.internal:3000"
# --- Printer ---
# Keep this token in sync with the Browserless TOKEN value.
BROWSERLESS_TOKEN="change-me"
PRINTER_ENDPOINT="ws://printer:3000?token=change-me"
# --- Database (PostgreSQL) ---
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/postgres"
@@ -124,7 +117,6 @@ S3_BUCKET=""
S3_FORCE_PATH_STYLE="false"
# --- Feature Flags ---
FLAG_DEBUG_PRINTER="false"
FLAG_DISABLE_SIGNUPS="false"
FLAG_DISABLE_EMAIL_AUTH="false"
FLAG_DISABLE_IMAGE_PROCESSING="false"
@@ -152,7 +144,7 @@ FLAG_DISABLE_IMAGE_PROCESSING="false"
</Step>
<Step title="Create compose.yml">
This setup runs Postgres + Printer + Reactive Resume on a private Docker network.
This setup runs Postgres and Reactive Resume on a private Docker network.
<CodeGroup>
@@ -173,22 +165,6 @@ services:
timeout: 5s
retries: 10
printer:
image: ghcr.io/browserless/chromium:latest
restart: unless-stopped
ports:
- "4000:3000"
environment:
- HEALTH=true
- CONCURRENT=20
- QUEUED=10
- TOKEN=${BROWSERLESS_TOKEN}
healthcheck:
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 10s
timeout: 5s
retries: 10
reactive-resume:
image: amruthpillai/reactive-resume:latest
# image: ghcr.io/amruthpillai/reactive-resume:latest
@@ -203,8 +179,6 @@ services:
depends_on:
postgres:
condition: service_healthy
printer:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
@@ -217,20 +191,6 @@ volumes:
</CodeGroup>
<Note>
**Alternative Printer Options**: If you don't want to use browserless, you can also use a lightweight headless Chrome Docker image like `chromedp/headless-shell`:
```yaml
chrome:
image: chromedp/headless-shell:latest
restart: unless-stopped
ports:
- "9222:9222"
```
Then set `PRINTER_ENDPOINT` to `http://chrome:9222` (or `http://localhost:9222` if running outside Docker Compose). This provides the same PDF/screenshot generation functionality with a smaller image footprint.
</Note>
<Tip>
Prefer pulling from Docker Hub? Keep <code>amruthpillai/reactive-resume:latest</code>. Prefer GHCR? Swap it to <code>ghcr.io/amruthpillai/reactive-resume:latest</code>.
</Tip>
@@ -277,9 +237,6 @@ docker compose logs -f reactive-resume
<li>
<code>DATABASE_URL</code>
</li>
<li>
<code>PRINTER_ENDPOINT</code>
</li>
<li>
<code>AUTH_SECRET</code>
</li>
@@ -311,28 +268,6 @@ docker compose logs -f reactive-resume
<Accordion title="Server">
- **`TZ`**: Sets the container timezone (affects logs and server-side timestamps). Recommended: `Etc/UTC`.
- **`APP_URL`**: Canonical/public URL for your instance (used for absolute URLs, redirects, and auth flows). If behind a reverse proxy, set this to your public HTTPS URL (for example, `https://resume.example.com`).
- **`PRINTER_APP_URL`** (optional): Overrides the base URL used when rendering the print route for the printer. Defaults to `APP_URL`. Useful when the printer must access the app via a different internal URL (for example, `http://host.docker.internal:3000`).
</Accordion>
<Accordion title="Printer">
- **`PRINTER_ENDPOINT`**: Endpoint where Reactive Resume connects to the printer browser.
- **Recommended (Browserless)**: `ws://printer:3000?token=...` and keep the token value in sync with Browserless `TOKEN`.
- **Also supported**: `http://chrome:9222` for Chrome DevTools Protocol endpoints.
<Note>
**Alternative to browserless**: You can use a lightweight headless Chrome Docker image like `chromedp/headless-shell`:
```yaml
chrome:
image: chromedp/headless-shell:latest
restart: unless-stopped
ports:
- "9222:9222"
```
Set `PRINTER_ENDPOINT` to `http://chrome:9222` (in Docker Compose) or `http://localhost:9222` (if running externally). This provides the same PDF/screenshot generation with a smaller image footprint.
</Note>
</Accordion>
<Accordion title="Database (PostgreSQL)">
@@ -406,7 +341,6 @@ openssl rand -hex 32
</Accordion>
<Accordion title="Feature Flags">
- **`FLAG_DEBUG_PRINTER`**: Bypasses the printer-only access restriction (useful when debugging `/printer/{resumeId}`). Recommended: keep `"false"` in production.
- **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances.
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/LinkedIn/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
- **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi.
@@ -460,7 +394,7 @@ If you're using S3-compatible storage, consider enabling versioning on your buck
## Health Checks
Reactive Resume exposes a health check endpoint at `/api/health` that verifies the application and its dependencies. It checks **database**, **printer**, and **storage**; if any one is unhealthy, the endpoint returns HTTP `503`.
Reactive Resume exposes a health check endpoint at `/api/health` that verifies the application and its dependencies. It checks **database** and **storage**; if either is unhealthy, the endpoint returns HTTP `503`.
### How it works
@@ -483,7 +417,7 @@ Most reverse proxies (such as **Traefik**, **Caddy**, or **nginx** with upstream
- **Healthy containers** receive traffic as normal
- **Unhealthy containers** are automatically removed from the load balancer pool
This is particularly useful in high-availability setups where you have multiple instances of Reactive Resume. If one instance becomes unhealthy (for example, it loses database, printer, or storage connectivity), the reverse proxy will stop routing traffic to it until it recovers.
This is particularly useful in high-availability setups where you have multiple instances of Reactive Resume. If one instance becomes unhealthy (for example, it loses database or storage connectivity), the reverse proxy will stop routing traffic to it until it recovers.
<Tip>
If you're using **Traefik**, it automatically respects Docker health checks when using the Docker provider. Unhealthy
@@ -522,16 +456,16 @@ A healthy response returns HTTP 200. Any other response (or a connection failure
restart the container.
</Accordion>
<Accordion title="PDF export fails / printing is stuck">
- **Common cause**: Reactive Resume can't reach the printer, token mismatch, or the printer can't reach your app. -
**Checks**: - `PRINTER_ENDPOINT` should usually be `ws://printer:3000?token=...` in Compose. - Browserless `TOKEN` and
the token in `PRINTER_ENDPOINT` must match. - If you use `PRINTER_APP_URL="http://host.docker.internal:3000"` on
Linux, set `extra_hosts: ["host.docker.internal:host-gateway"]` for the printer service.
<Accordion title="PDF export fails or downloads an empty file">
- **Common cause**: PDFs are now rendered in the browser via `@react-pdf/renderer`, so failures usually come from a
blocked download, an extreme browser memory limit, or a custom CSP that strips inline workers. - **Checks**: confirm
the browser is up to date, the page hasn't been opened in a restricted iframe, and that no extension is intercepting
the download. There is no server-side printer to inspect.
</Accordion>
<Accordion title="/api/health returns 503 even though Postgres is up">
- **Common cause**: printer or storage health failed (not only database). - **Fix**: inspect the endpoint response
payload and check `printer` / `storage` fields: ```bash curl -s http://localhost:3000/api/health ```
- **Common cause**: storage health failed (not only database). - **Fix**: inspect the endpoint response payload and
check the `storage` field: ```bash curl -s http://localhost:3000/api/health ```
</Accordion>
<Accordion title="Uploads disappear after restart">
+6 -65
View File
@@ -3,6 +3,10 @@ title: "Docker Compose Examples"
description: "A collection of Docker Compose examples for different deployment scenarios. If you have a different setup that works for you, please share it by opening a pull request on GitHub."
---
<Info>
**From v5.1.0 onwards** — PDF generation now runs entirely client-side via `@react-pdf/renderer`. None of the examples below require a Browserless or Chromium service. Older configurations that still define a `printer` service or set `BROWSERLESS_TOKEN` / `PRINTER_*` will continue to start, but those services are inert and can be removed.
</Info>
## Overview
Every self-hosted setup is unique. You might be running on a single VPS, a Kubernetes cluster, behind Cloudflare Tunnel, or using a specific reverse proxy like Traefik or nginx. This page provides real-world Docker Compose configurations for various deployment scenarios to help you get started faster.
@@ -19,7 +23,7 @@ These examples go beyond the basic setup in the [Self-Hosting with Docker](/self
## Docker with Traefik
This example uses [Traefik](https://traefik.io/) as a reverse proxy with automatic SSL certificate management via Let's Encrypt. Only the Reactive Resume app is exposed through Traefik—Postgres and the printer remain on an internal network.
This example uses [Traefik](https://traefik.io/) as a reverse proxy with automatic SSL certificate management via Let's Encrypt. Only the Reactive Resume app is exposed through Traefik—Postgres remains on an internal network.
<Tip>
Traefik automatically discovers services via Docker labels and handles SSL certificates, making it ideal for setups
@@ -78,30 +82,12 @@ services:
timeout: 5s
retries: 5
printer:
image: ghcr.io/browserless/chromium:latest
restart: unless-stopped
environment:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
- TOKEN=${BROWSERLESS_TOKEN}
networks:
- reactive_resume_network
healthcheck:
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
reactive_resume:
image: amruthpillai/reactive-resume:latest
restart: unless-stopped
environment:
- APP_URL=https://resume.${DOMAIN}
- PRINTER_APP_URL=http://reactive_resume:3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- PRINTER_ENDPOINT=ws://printer:3000?token=${BROWSERLESS_TOKEN}
- AUTH_SECRET=${AUTH_SECRET}
# Add other optional env vars as needed (SMTP, S3, OAuth, etc.)
volumes:
@@ -111,8 +97,6 @@ services:
depends_on:
postgres:
condition: service_healthy
printer:
condition: service_healthy
labels:
- "traefik.enable=true"
- "traefik.http.routers.reactive-resume.rule=Host(`resume.${DOMAIN}`)"
@@ -142,7 +126,6 @@ DOMAIN="example.com"
ACME_EMAIL="admin@example.com"
POSTGRES_PASSWORD="your-secure-postgres-password"
AUTH_SECRET="your-auth-secret-from-openssl-rand-hex-32"
BROWSERLESS_TOKEN="your-browserless-token"
# Optional: Traefik dashboard auth (generate with: htpasswd -nb admin password)
TRAEFIK_DASHBOARD_AUTH="admin:$$apr1$$..."
```
@@ -184,30 +167,12 @@ services:
timeout: 5s
retries: 5
printer:
image: ghcr.io/browserless/chromium:latest
restart: unless-stopped
environment:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
- TOKEN=${BROWSERLESS_TOKEN}
networks:
- reactive_resume_network
healthcheck:
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=${BROWSERLESS_TOKEN}" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
reactive_resume:
image: amruthpillai/reactive-resume:latest
restart: unless-stopped
environment:
- APP_URL=https://resume.${DOMAIN}
- PRINTER_APP_URL=http://reactive_resume:3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
- PRINTER_ENDPOINT=ws://printer:3000?token=${BROWSERLESS_TOKEN}
- AUTH_SECRET=${AUTH_SECRET}
# Add other optional env vars as needed (SMTP, S3, OAuth, etc.)
volumes:
@@ -217,8 +182,6 @@ services:
depends_on:
postgres:
condition: service_healthy
printer:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
@@ -285,7 +248,7 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeouts for long-running requests (PDF generation)
# Reasonable timeouts for app requests
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
@@ -335,25 +298,6 @@ services:
mode: replicated
replicas: 1
printer:
image: ghcr.io/browserless/chromium:latest
networks:
- reactive_resume_network
environment:
- QUEUED=10
- HEALTH=true
- CONCURRENT=5
- TOKEN=$BROWSERLESS_TOKEN
healthcheck:
test: ["CMD-SHELL", 'curl -fsS "http://localhost:3000/pressure?token=$BROWSERLESS_TOKEN" > /dev/null']
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
mode: replicated
replicas: 1
seaweedfs:
image: chrislusf/seaweedfs:latest
command: server -s3 -filer -dir=/data -ip=0.0.0.0
@@ -399,9 +343,6 @@ services:
- reactive_resume_data:/app/data
environment:
- APP_URL=$APP_URL
# If using browserless with token auth, include the token in the URL:
# PRINTER_ENDPOINT=ws://printer:3000?token=your-secret-token
- PRINTER_ENDPOINT=$PRINTER_ENDPOINT
- DATABASE_URL=$DATABASE_URL
- AUTH_SECRET=$AUTH_SECRET
- GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID
+6 -2
View File
@@ -90,8 +90,12 @@ To run the migration scripts, you need the following installed on your host mach
Alternatively, you can use `dotenv`, `direnv`, or export the variables manually.
</Card>
<Card title="Reactive Resume Source Code">
Clone the Reactive Resume repository to access the migration scripts: ```bash git clone
https://github.com/amruthpillai/reactive-resume.git cd reactive-resume vp install ```
Clone the Reactive Resume repository to access the migration scripts:
```bash
git clone https://github.com/amruthpillai/reactive-resume.git
cd reactive-resume
pnpm install
```
</Card>
</CardGroup>