Skip to content

Docker Compose deployment

The repo ships two Compose files:

  • docker-compose.yml — local dev. Just PostgreSQL on port 5433.
  • docker-compose.prod.yml — full stack: Postgres + API + web. Production-style.
Terminal window
cp .env.prod.example .env

Edit .env. The required values:

VariableWhy
SECRET_KEYSigns the session JWT. Use a long random string.
POSTGRES_PASSWORDPostgres user password.
MARROW_API_URLThe URL the browser will call to reach the API (e.g. https://api.example.com).

See Environment variables for the full reference, including OIDC and CORS.

Pin a release tag so the API image and web source match:

Terminal window
git fetch --tags
git checkout v0.4.0 # recommended for production

Set MARROW_VERSION in .env to the same tag (e.g. v0.4.0), then:

Terminal window
docker compose -f docker-compose.prod.yml up -d --build

This:

  1. Brings up PostgreSQL with a persistent volume (postgres_data).
  2. Pulls the API image from GHCR (ghcr.io/marrow-software/marrow-api:${MARROW_VERSION}) and runs alembic upgrade head then uvicorn.
  3. Builds the web image from ./web in the checked-out tree. The browser-visible config (MARROW_API_URL, MARROW_API_KEY, MARROW_OIDC_ENABLED) is read from container env at startup and written into /config.js — no rebuild needed when only those vars change.

The API exposes port 8000 and the web exposes port 3000 by default. Override with API_PORT / WEB_PORT.

Terminal window
curl http://localhost:8000/health

Open http://localhost:3000 — you should see the workspace list.

Two volumes hold all state:

  • postgres_data — the database.
  • api_storage — uploaded attachments (mounted at /data/storage inside the API container).

Back up both for a complete restore. Or use marrow export for portable bundles — see Restore guarantee.

The Compose file does not include a reverse proxy. In production, terminate TLS in front of the web container with Caddy, Traefik, or nginx, and point MARROW_API_URL at the public API hostname.

If the API and web are on different subdomains, set CORS_ORIGINS to the web origin and COOKIE_DOMAIN to the parent domain (e.g. .example.com) so the session cookie is shared.

Check out the new release, update MARROW_VERSION in .env to the same tag, then pull the API image and rebuild the web container:

Terminal window
git fetch --tags
git checkout v0.4.0 # pick the release you want
# edit .env — set MARROW_VERSION=v0.4.0 to match
docker compose -f docker-compose.prod.yml pull api
docker compose -f docker-compose.prod.yml up -d --build

Migrations run automatically on API container start.

If you skip updating MARROW_VERSION, pull api keeps resolving the old pinned tag while the web container rebuilds from the newly checked-out source — API and web versions can diverge.