Skip to content
TulipFarmDocs

Last updated

Run TulipFarm with Docker Compose

Install, upgrade, back up, restore, and troubleshoot a single-machine TulipFarm install with the bundled Docker Compose stack.

self-host/compose.yaml runs the whole product on one machine. It includes PostgreSQL, S3-compatible object storage, and Temporal. Read the overview first.

  1. Clone the repository and enter self-host/:

    Terminal window
    git clone https://github.com/TulipFarm/tulipfarm.git
    cd tulipfarm/self-host
  2. Supply the operator settings. Pick one method:

    • Plain file: cp .env.example .env, fill it in, then chmod 600 .env.
    • Secret store: store the same names in Doppler, Infisical, 1Password, or Vault. See Secret stores.
    • Encrypted file: encrypt the file with SOPS and your KMS. See KMS.

    Set TULIPFARM_RELEASE_COMMIT to the output of git rev-parse HEAD. Generate passwords with openssl rand -hex 32.

  3. Start the stack. Prefix the command with your secret-store runner if you use one, for example doppler run -- docker compose up -d --build:

    Terminal window
    docker compose up -d --build
  4. Check health:

    Terminal window
    docker compose ps
    curl https://tulipfarm.example.com/v1/health

    Every service reports "status":"ok". setup, temporal-schema, and temporal-namespace exit with code 0. The others keep running.

postgres ─┬─▶ setup ─┬─▶ temporal-schema ─▶ temporal ─▶ temporal-namespace ─┐
storage ─┘ └──────────────────────────────────────────────────────┴─▶ api, worker, storage-worker ─▶ web

setup is idempotent and runs on every up. It:

  1. Creates state.json with generated passwords and keys, once.
  2. Creates roles and the tulipfarm, temporal, and temporal_visibility databases.
  3. Installs extensions and applies migrations.
  4. Creates restricted logins for workspace storage and email delivery.
  5. Creates the object-storage bucket.
  6. Writes api.env, worker.env, storage-worker.env, and temporal.env with mode 0600.

All files go to the generated volume. Each service mounts it read-only. Node services load their file with node --env-file. A variable already set in the container environment takes precedence over the file.

web runs Caddy. When TULIPFARM_SITE_ADDRESS is a domain name, Caddy obtains and renews certificates itself. Ports 80 and 443 must be reachable.

If TulipFarm runs behind another proxy or load balancer:

Terminal window
TULIPFARM_SITE_ADDRESS=:80
TULIPFARM_HTTP_PORT=8080 # your proxy forwards here

Keep TULIPFARM_PUBLIC_URL as the public HTTPS origin. The API uses API_PROXY_MODE=reverse-proxy. It trusts only the rightmost X-Forwarded-For hop, which Caddy appends.

The Temporal UI listens on 127.0.0.1:8233 only. Reach it through an SSH tunnel:

Terminal window
ssh -L 8233:127.0.0.1:8233 you@your-server
Terminal window
git pull
# Set TULIPFARM_RELEASE_VERSION and TULIPFARM_RELEASE_COMMIT to the new release.
docker compose up -d --build

setup applies new migrations before the application services restart. Back up first. Downgrades are not supported. Restore a backup instead.

Back up three things together:

What Why Command
state.json Holds every generated password and encryption key See below
PostgreSQL All application, workspace, and Temporal data docker compose exec -T postgres pg_dumpall -U postgres > tulipfarm.sql
object-storage-data volume Workspace repositories Copy the volume, or use your storage service’s backups

Copy state.json out of the volume. Encrypt it before storing it; see KMS:

Terminal window
docker compose run --rm -T --no-deps --entrypoint cat setup /setup/state.json > state.json
chmod 600 state.json

pg_dumpall output contains password hashes and encrypted credentials. Protect it like state.json.

Restore state.json before the first setup run on the new host:

Terminal window
docker compose up -d postgres object-storage
docker compose run --rm -T --no-deps --entrypoint sh setup \
-c 'umask 077 && cat > /setup/state.json' < state.json
# Restore PostgreSQL and object storage, then:
docker compose up -d

Setup then resets each role to the password in state.json.

Terminal window
docker compose down # keeps data
docker compose down -v # deletes all data, including state.json
Symptom Check
required variable … is missing The setting is not in the shell environment or self-host/.env.
setup exits 1 docker compose logs setup, then docker compose logs postgres. Setup never prints credentials.
Sign-in fails Turnstile keys must be real and allow your hostname. The Google redirect URI must be <public URL>/v1/auth/callback/google.
/v1/health reports workers down docker compose logs worker storage-worker.

Was this page helpful?