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).
Topology
Section titled “Topology”The stack is two services on a single bridge network (vibexp):
postgres
Section titled “postgres”image: pgvector/pgvector:pg17PostgreSQL 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:latestports: ["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.
How the env vars work
Section titled “How the env vars work”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:roRunning without Compose (docker run)
Section titled “Running without Compose (docker run)”The combined image is self-contained, so a single container works anywhere a PostgreSQL with pgvector is reachable:
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.0The 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.
Image tags
Section titled “Image tags”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.
Volume
Section titled “Volume”volumes: pgdata:The named pgdata volume holds the database. It survives
docker compose down. To wipe all data, use docker compose down -v.
Network
Section titled “Network”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).
Optional: GCS emulator for attachments
Section titled “Optional: GCS emulator for attachments”The compose file ships a commented-out gcs service
(fsouza/fake-gcs-server) for persistent file attachments. To enable uploads:
- Uncomment the
gcsservice and itsgcsdatavolume. - Uncomment
STORAGE_EMULATOR_HOSTandGCS_RESOURCE_ATTACHMENTS_BUCKETon theappservice.
Without it, attachment uploads are disabled.
Lifecycle
Section titled “Lifecycle”docker compose up -d # start (pulls the published image on first run)docker compose ps # check statusdocker compose logs -f # follow logsdocker compose down # stop and remove containers (keeps pgdata)docker compose down -v # stop and ALSO delete the pgdata volume (wipes data)