Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Docker Images

Pre-built images are published to the GitHub Container Registry on every push to main:

ghcr.io/roostorg/coop-server       # API server
ghcr.io/roostorg/coop-worker       # Background worker
ghcr.io/roostorg/coop-client       # Frontend (nginx)
ghcr.io/roostorg/coop-migrations   # One-shot database migrations runner

Images are tagged with latest, the git SHA, and semver tags on release. The coop-migrations image is only rebuilt when something under db/ changes (e.g. a new migration script) or on a release, so its tags track the schema rather than every server/client change.

Quick start

Pull the published images and run the full stack locally with a single command:

docker compose -f docker-compose.images.yaml up -d

Warning

docker-compose.images.yaml uses .env.docker, which ships working defaults for local evaluation but includes placeholder secrets. Review and replace secrets before any non-local deployment.

This starts:

  • Coop server on port 8080
  • Coop client (nginx) on port 3000
  • Postgres (pgvector), Redis, ScyllaDB, ClickHouse
  • Database migrations (run automatically)
  • A seed service that creates an admin user with a randomly generated password

Get your login credentials

The seed service prints the generated credentials to its logs:

docker compose -f docker-compose.images.yaml logs seed

Look for the output at the bottom:

============================================
  Login:    admin@coop.local
  Password: <randomly-generated>
============================================

Then open http://localhost:3000 and log in.

On subsequent startups (with the same volumes), the seed service detects the existing user and skips — your credentials remain the same. If you lose the password, tear down with -v to reset.

Create additional users

docker compose -f docker-compose.images.yaml exec server \
  node bin/create-org-and-user.js \
  --name "My Org" \
  --email "you@example.com" \
  --website "https://example.com" \
  --firstName "Jane" \
  --lastName "Doe" \
  --password "your-password"

Tear down

# Stop containers, keep data volumes
docker compose -f docker-compose.images.yaml down

# Stop containers and wipe all data
docker compose -f docker-compose.images.yaml down -v

Image details

ImageDockerfileBuild targetBase
coop-serverDockerfilebuild_servernode:24-bullseye-slim + dumb-init
coop-workerDockerfilebuild_worker_runnernode:24-bullseye-slim + dumb-init
coop-clientclient/Dockerfileservenginx:1.27-bookworm
coop-migrationsdb/Dockerfile(final stage)node:24-bullseye-slim

The client image serves the Vite-built SPA via nginx and proxies /api/ requests (including /api/v1/graphql) to a backend service named server on port 8080.

Running migrations

The coop-migrations image bundles the migration scripts under db/ together with the migrator engine, so it can run as a one-shot task (an ECS RunTask, a Kubernetes Job, or the migrations service in docker-compose.images.yaml). It exposes the same npm run db:* commands used in local development, so the invocation matches what you’d run from the repo root:

# Apply all pending migrations to an existing prod database
docker run --rm --env-file .env.docker ghcr.io/roostorg/coop-migrations:latest \
  npm run db:update -- --db api-server-pg --env prod

Supported --db values are api-server-pg, scylla, and clickhouse. Connection settings come from environment variables (see db/.env.example), which must be present for any command since the database configs are read at startup.

What --env controls

--env (staging or prod) only affects seed scripts, not migrations:

  • Migration scripts run in every environment.
  • A seed named *.seed.<env>.sql runs only when --env matches it. Seed files are timestamp-prefixed; the repo currently ships db/src/scripts/api-server-pg/2025.12.01T00.00.01.initial-test-data.seed.staging.sql, which creates a sample org with default-password users — so use --env prod in production to skip it. Running --env staging against a prod database would seed that test data.
  • db:create ignores --env functionally and is allowed in prod, so you can provision schemas for self-hosted Scylla/ClickHouse (which have no managed “create the database” step).
  • db:clean and db:drop are destructive and reject --env prod as a safety guard.