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.
1. Configure
Section titled “1. Configure”cp .env.prod.example .envEdit .env. The required values:
| Variable | Why |
|---|---|
SECRET_KEY | Signs the session JWT. Use a long random string. |
POSTGRES_PASSWORD | Postgres user password. |
MARROW_API_URL | The 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.
2. Check out a release and start
Section titled “2. Check out a release and start”Pin a release tag so the API image and web source match:
git fetch --tagsgit checkout v0.4.0 # recommended for productionSet MARROW_VERSION in .env to the same tag (e.g. v0.4.0), then:
docker compose -f docker-compose.prod.yml up -d --buildThis:
- Brings up PostgreSQL with a persistent volume (
postgres_data). - Pulls the API image from GHCR (
ghcr.io/marrow-software/marrow-api:${MARROW_VERSION}) and runsalembic upgrade headthenuvicorn. - Builds the web image from
./webin 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.
3. Verify
Section titled “3. Verify”curl http://localhost:8000/healthOpen http://localhost:3000 — you should see the workspace list.
Volumes
Section titled “Volumes”Two volumes hold all state:
postgres_data— the database.api_storage— uploaded attachments (mounted at/data/storageinside the API container).
Back up both for a complete restore. Or use marrow export for portable bundles — see Restore guarantee.
Reverse proxy
Section titled “Reverse proxy”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.
Updating
Section titled “Updating”Check out the new release, update MARROW_VERSION in .env to the same tag, then pull the API image and rebuild the web container:
git fetch --tagsgit checkout v0.4.0 # pick the release you want# edit .env — set MARROW_VERSION=v0.4.0 to matchdocker compose -f docker-compose.prod.yml pull apidocker compose -f docker-compose.prod.yml up -d --buildMigrations 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.