Skip to main content

Deploy with Docker

Parcelis ships published container images for the application. The simplest way to run the platform is to use Docker directly when you already have PostgreSQL, Redis, and S3-compatible storage services available.

danger

Existing PostgreSQL 16 installations

For deployments using the supplied Compose files, PostgreSQL 18 is now required. Before running up with the updated files, follow Upgrade PostgreSQL 16 to 18. These files preserve existing volumes and block startup until the data migration is verified.

For Docker-direct deployments using an external database, use your provider's major-upgrade process or a reviewed PostgreSQL dump/restore procedure. The migration helper only supports the supplied Compose named-volume layout; external databases have no Compose readiness marker. Prisma migrations do not upgrade the database server.

Requirements​

To run Parcelis with Docker, the minimum requirements are simply those required to install and run Docker. The production Compose stack includes nginx as the proxy for the web app and API.

Setting up Parcelis​

Generate NEXTAUTH_SECRET once with openssl rand -base64 32 and save it in your deployment's secret store. Export that saved value before running the command below. Reuse it on every restart, redeploy, and replica; changing it invalidates existing sessions.

The examples use latest, matching the supplied Compose files. Use compatible app and proxy images from the same application revision; the direct-routing layout is introduced by application PR #582. Build them locally when matching published images are unavailable. Apply any required database migrations before starting application services. Older releases require their matching deployment instructions.

The modular app no longer contains nginx or serves a combined endpoint on port 3000. The separate proxy routes to web on 3001 and API on 4000. Use the Compose guide to provision the complete stack, or attach all containers to an existing user-defined Docker network with access to PostgreSQL and Redis. The examples call that network parcelis; create it once if needed:

docker network create parcelis

Start the app with the app-service network alias expected by the proxy. Do not publish its web or API ports to the host:

docker run -d --name parcelis-app --network parcelis --network-alias app-service --stop-timeout 25 \
-e DATABASE_URL=postgresql://parcelis:parcelis@postgres:5432/parcelis?schema=public \
-e WEB_ORIGIN=https://parcelis.example.com \
-e NEXTAUTH_URL=https://parcelis.example.com \
-e API_TRUST_PROXY_HOPS=1 \
-e NEXTAUTH_SECRET="${NEXTAUTH_SECRET:?Set NEXTAUTH_SECRET to your saved deployment secret}" \
-e REDIS_HOST=redis \
-e REDIS_PORT=6379 \
-e REDIS_PASSWORD=your-redis-password \
-e EMAIL_SETTINGS_ENCRYPTION_KEY=your-base64-32-byte-key \
-e EMAIL_FROM='Parcelis <no-reply@example.com>' \
-e SMTP_HOST=smtp.example.com \
-e SMTP_PORT=465 \
-e SMTP_SECURE=true \
-e SMTP_USER=your-smtp-user \
-e SMTP_PASSWORD=your-smtp-password \
-e S3_ENDPOINT=https://s3.example.com \
-e S3_PUBLIC_ENDPOINT=https://s3.example.com \
-e S3_BUCKET=parcelis-images \
-e S3_ACCESS_KEY_ID=your-access-key \
-e S3_SECRET_ACCESS_KEY=your-secret-key \
parcelis/apps:latest

For a proxy configuration that forwards /docs/ to docs-service, including the configuration in PR #582, start the matching documentation container on the same network:

docker run -d --name parcelis-docs --network parcelis --network-alias docs-service parcelis/docs:latest

A proxy build that redirects documentation URLs to the hosted documentation site does not need that container. Check the configuration in the selected application revision.

Wait for parcelis-app to report healthy, then start the proxy as the public entry point:

docker run -d --name parcelis-proxy --network parcelis -p 3000:80 parcelis/proxy:latest

The proxy routes / to web, /api/*, /trpc/*, and /admin/jobs/* to API, and /healthz to API health. Its health check covers both web and API routing. Generated redirects remain relative so externally mapped ports are preserved. Set public origin variables for the browser-facing address and configure TLS termination for HTTPS.

Background jobs require the separate worker described below. The production Compose stack starts these services and dependencies together:

docker compose --env-file .env.production up -d

For a single-image deployment that bundles the web app, API, worker, and nginx routing into one image, with a separate worker container using that same image, see Deploy with the Single-Image Docker Build.

warning

If you are planning to run Parcelis in a production environment, update the values for the database, object storage, and authentication settings before you expose the service publicly. Use strong, unique credentials and keep them out of shell history and version control.

Background worker​

The modular application image excludes the worker. Start the dedicated worker on the same Docker network as PostgreSQL and Redis, using the saved email settings from the app. Add --network with that network's name to the examples when using container hostnames. Export the saved database, Redis, and email settings before starting the worker:

docker run -d --name parcelis-worker --stop-timeout 25 \
-e DATABASE_URL="$DATABASE_URL" \
-e REDIS_HOST="$REDIS_HOST" \
-e REDIS_PORT="${REDIS_PORT:-6379}" \
-e REDIS_PASSWORD="$REDIS_PASSWORD" \
-e EMAIL_FROM="$EMAIL_FROM" \
-e SMTP_HOST="$SMTP_HOST" \
-e SMTP_PORT="$SMTP_PORT" \
-e SMTP_SECURE="$SMTP_SECURE" \
-e SMTP_USER="$SMTP_USER" \
-e SMTP_PASSWORD="$SMTP_PASSWORD" \
-e EMAIL_SETTINGS_ENCRYPTION_KEY="$EMAIL_SETTINGS_ENCRYPTION_KEY" \
parcelis/worker:latest

Apply database migrations before starting either container. The worker starts Node directly, exposes no public port, and reports readiness through Redis heartbeats and canary jobs in the status monitor. A running container alone does not confirm job processing. Keep EMAIL_SETTINGS_ENCRYPTION_KEY identical to the API's saved key. It has no web HTTP health check. The single-image deployment runs its worker container from parcelis/parcelis:latest with pnpm --filter @parcelis/worker start.

Upgrading an existing deployment​

The NextAuth upgrade removes the database Session table. Previous application instances still query it, so use a maintenance window. Back up PostgreSQL, drain traffic from every application replica, and wait for in-flight requests to finish. Stop parcelis-app and every other old application and worker instance before applying migrations. Keep PostgreSQL, Redis, and object storage running.

Obtain matching parcelis/apps:latest and parcelis/proxy:latest builds, plus the worker image when upgrading it. Stop the old app and proxy together before switching to direct routing, and set API_TRUST_PROXY_HOPS=1 for the supplied one-proxy topology. See the routing migration steps. Run any applicable database migrations from the application image with your production connection:

docker run --rm \
-e DATABASE_URL="$DATABASE_URL" \
-w /repo \
parcelis/apps:latest \
pnpm --filter @parcelis/db exec prisma migrate deploy --schema prisma/schema.prisma

Use the same Docker network as your database when needed. If migration fails, keep traffic disabled until the failure is resolved. Recreate the app, proxy, and any upgraded worker containers from their matching new images with the saved environment settings, verify their health, then restore traffic. Users must sign in again. The previous release cannot run against the migrated database; reverting requires restoring the pre-upgrade database backup. For Compose, follow the Compose upgrade sequence.

Required environment variables​

The app container expects the following values at minimum:

  • DATABASE_URL: PostgreSQL connection string
  • REDIS_HOST, REDIS_PORT, and REDIS_PASSWORD: authenticated Redis connection for background jobs
  • WEB_ORIGIN: the public origin used by the web app
  • NEXTAUTH_URL: the public Parcelis origin, including HTTPS
  • NEXTAUTH_SECRET: a private value generated once with openssl rand -base64 32, persisted across redeploys and shared across API instances
  • EMAIL_FROM: sender address for transactional email
  • SMTP_HOST, SMTP_PORT, and SMTP_SECURE: SMTP server connection settings; SMTP_SECURE must be true or false
  • SMTP_USER and SMTP_PASSWORD: SMTP authentication credentials
  • EMAIL_SETTINGS_ENCRYPTION_KEY: base64-encoded 32-byte key used to encrypt saved organization SMTP passwords
  • S3_ENDPOINT and S3_PUBLIC_ENDPOINT: S3-compatible endpoint URLs
  • S3_BUCKET: the object-storage bucket used for uploaded images
  • S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY: credentials for object storage

The nginx proxy exposes the web app at /, the API at /api/v1, and documentation according to the selected proxy configuration. The modular app has no public nginx endpoint.

Internal routing and proxy trust​

API_TRUST_PROXY_HOPS=1 matches the supplied path from browser through one Parcelis nginx to API. If another trusted reverse proxy sits in front, adjust this value for that path and keep direct API access restricted. The API's default outside Compose is 0.

Both application targets default API_INTERNAL_URL to the API loopback address and configured port for organization-access checks. If customizing API_INTERNAL_PORT, build the app and proxy with the same argument. The single-image build keeps bundled nginx on 3000 and does not require the separate proxy image.

Optional environment variables​

For the complete .env reference, including TLS and STARTTLS guidance, see Email configuration. Organization administrators can configure SMTP in Settings → Organization → Email. Those settings take precedence for email sent to a user whose default organization has a saved configuration. The environment SMTP values remain the fallback.

Feature flags​

Feature flags control which optional features are available. Unset flags default to disabled; only the exact value true enables them.

VariableDefaultDescription
FEATURE_FLAG_APPLICATIONS_ENABLEDfalseEnables application navigation, pages, templates, sent requests, status management, application notes, and role permission controls.

To enable applications, add this to .env for local development or .env.production for Docker Compose:

FEATURE_FLAG_APPLICATIONS_ENABLED="true"

Provide the same value to both the web app and API. Restart local processes or recreate containers after changing it. When disabled, application pages and API operations are unavailable, including for administrators. Existing application data and role permissions are preserved.

For Docker Compose, app-service.environment in docker-compose.yml must include the mapping below. The repository's Compose file includes it; add it if using an older or custom file. An entry in .env.production alone does not pass the variable into the container.

services:
app-service:
environment:
FEATURE_FLAG_APPLICATIONS_ENABLED: ${FEATURE_FLAG_APPLICATIONS_ENABLED:-false}

For local development, docker-compose-dev.yml runs supporting services only. The web app and API run on the host; pnpm dev loads the root .env and passes its values to those processes. Set the flag in .env and restart pnpm dev; no flag mapping is needed in the dev Compose file.

Apply the changed environment with docker compose --env-file .env.production up -d --force-recreate app-service. For docker run, pass -e FEATURE_FLAG_APPLICATIONS_ENABLED=true.

When applications are disabled, auth.me and roles.list keep the permission matrix's resource keys but report all application and application-note actions as false. Role permission updates ignore those resources until the feature is enabled, preserving their stored permissions.