Skip to content

Docker & Compose

This page walks through the root docker-compose.yml, which runs the published VibeXP image. It is for running VibeXP; local development uses make instead (see Getting Started).

The stack is two services on a single bridge network (vibexp):

image: pgvector/pgvector:pg17

PostgreSQL 17 with the pgvector extension (needed for semantic search). It has a pg_isready healthcheck, and the app service waits on condition: service_healthy before starting. Data lives in the pgdata named volume.

image: ghcr.io/vibexp/vibexp:latest
ports: ["8080:8080"]

The combined image: the Go backend embeds the built frontend SPA and serves both from one binary — the SPA at /, the REST API at /api/v1, and the MCP endpoint, all on a single port and a single origin. There is no separate frontend container, no reverse proxy in between, and no CORS to configure.

Its environment: block carries the database connection (DB_HOST: postgres, DB_USER, DB_PASSWORD, DB_NAME), the required ENCRYPTION_KEY, plus SESSION_ENCRYPTION_KEY (needed for production session auth; empty disables cookie sessions) and the public origin (FRONTEND_BASE_URL). Other baked operator knobs include DB_SSLMODE (require for managed Postgres TLS), INSTANCE_ADMIN_EMAILS (grants the admin portal), and AUTH_ALLOWED_DOMAINS / AUTH_ALLOWED_EMAILS (restrict sign-in). Its healthcheck hits http://localhost:8080/ping. See Configuration Reference for which of these you must change for production.

The image bakes a default configuration file (backend/config.docker.yaml) at /app/config.yaml and points VIBEXP_CONFIG_FILE at it. Every operator knob in that file is a ${VAR:-default} reference, so the environment: block configures the container with plain env vars — no config file to author. Only secrets and non-default knobs need to be set; anything omitted falls back to the baked default.

Taking full control: mount your own config.yaml

Section titled “Taking full control: mount your own config.yaml”

To control every setting, mount your own file over the baked path and keep only secrets in the environment. Copy backend/config.example.yaml (the complete, commented field list), edit it, and uncomment the volumes: on the app service:

volumes:
- ./config.yaml:/app/config.yaml:ro

The combined image is self-contained, so a single container works anywhere a PostgreSQL with pgvector is reachable:

Terminal window
docker run -p 8080:8080 \
-e DB_HOST=your-db-host -e DB_PASSWORD=secret \
-e ENCRYPTION_KEY="$(openssl rand -base64 24 | cut -c1-32)" \
-e FRONTEND_BASE_URL=https://vibexp.example.com \
ghcr.io/vibexp/vibexp:0.10.0

The baked FRONTEND_BASE_URL defaults to empty (fail-closed: the dev-login bypass stays off). To evaluate locally with the dev-login shortcut via a bare docker run, add -e FRONTEND_BASE_URL=http://localhost:8080 — or just use docker compose up, which sets it for you.

Each GitHub Release with a vX.Y.Z tag publishes ghcr.io/vibexp/vibexp:X.Y.Z (e.g. ghcr.io/vibexp/vibexp:0.10.0). Since v0.4.0 the image is multi-arch: one manifest covers linux/amd64 and linux/arm64.

:latest, which docker-compose.yml tracks, points at the highest published version, not the most recent build. A prerelease never moves it, and neither does a backport patch on an older line: publishing 0.9.1 after 0.10.0 is out leaves :latest on 0.10.0 rather than downgrading everyone who tracks it.

volumes:
pgdata:

The named pgdata volume holds the database. It survives docker compose down. To wipe all data, use docker compose down -v.

Both services share a single user-defined bridge network named vibexp, so they reach each other by service name (the app connects to postgres:5432).

The compose file ships a commented-out gcs service (fsouza/fake-gcs-server) for persistent file attachments. To enable uploads:

  1. Uncomment the gcs service and its gcsdata volume.
  2. Uncomment STORAGE_EMULATOR_HOST and GCS_RESOURCE_ATTACHMENTS_BUCKET on the app service.

Without it, attachment uploads are disabled.

Terminal window
docker compose up -d # start (pulls the published image on first run)
docker compose ps # check status
docker compose logs -f # follow logs
docker compose down # stop and remove containers (keeps pgdata)
docker compose down -v # stop and ALSO delete the pgdata volume (wipes data)