Skip to content
Docs
opendesk.shOpen app

Docker Compose

The services, profiles and settings in opendesk's self-hosted docker-compose.yml.

The installer is a convenience around Docker Compose. The same docker-compose.yml is at the root of the repository and is published with every release, so you can run it yourself.

Service Image Role
caddy caddy:2.11-alpine Ports 80 and 443 with automatic HTTPS. /socket* goes to realtime, everything else to opendesk.
opendesk ghcr.io/useopendesk/opendesk:${OPENDESK_VERSION} The API, workers and web app (SERVE_WEB=true), with NATS embedded on the internal network.
realtime ghcr.io/useopendesk/realtime:${OPENDESK_VERSION} The Phoenix gateway, connected to opendesk’s NATS with NATS_TOKEN.
postgres pgvector/pgvector:pg17 Postgres 17 with pgvector for semantic search, data in the postgres-data volume.

Postgres, NATS and the app ports are on an internal network and are never published. Only Caddy (and the optional SMTP relay) face the host.

A self-hosted install on one server and one domain. Browsers reach Caddy over HTTPS on port 443. Caddy sends /socket to the realtime gateway and everything else to opendesk. opendesk, which runs the API, the workers, the web app and an embedded NATS, publishes events over NATS to the realtime gateway and keeps its data in Postgres. Realtime, opendesk and Postgres sit on an internal network; only Caddy is published.desk.example.com, one serverinternal networkBrowserweb app, appsand chat widgetcaddyHTTPS, ports 80, 443realtimelive events, presenceopendeskAPI, workers, web appNATS, embeddedpostgresthe only datastore:443/socketeverything elseevents over NATSSQLthe only published service
Caddy is the only way in. realtime, opendesk and postgres share the internal network, and the gateway reaches opendesk's embedded NATS there.

Every service runs with no-new-privileges, and the app containers run with a read-only root filesystem and all Linux capabilities dropped. Logs rotate at 10 MB, five files each.

  1. Download the release files

    From the GitHub release for the version you want, download docker-compose.yml, Caddyfile, env.template, opendesk-ctl and SHA256SUMS into one directory, then check them:

    Terminal window
    mkdir -p /opt/opendesk && cd /opt/opendesk
    sha256sum -c SHA256SUMS --ignore-missing
  2. Write .env

    Terminal window
    cp env.template .env && chmod 600 .env

    Fill in the address and generate the secrets (see The .env file).

  3. Pull the images and start

    Terminal window
    docker compose pull
    docker compose up -d

From a checkout, make selfhost-env writes .env with fresh secrets (it never overwrites one), and docker compose up -d --build builds the images from source instead of pulling them. Every app service has both image: and build:, so docker compose pull && docker compose up -d compiles nothing.

.env sits next to docker-compose.yml. The template (deploy/self-host/.env.template) documents each setting. The ones specific to this bundle:

Variable Default Meaning
OPENDESK_VERSION pinned by the installer Release of the opendesk and realtime images. Change it, then docker compose pull && docker compose up -d, to upgrade.
OPENDESK_IMAGE, REALTIME_IMAGE ghcr.io/useopendesk/... Image names, for a mirror.
OPENDESK_DOMAIN localhost The hostname Caddy serves. localhost uses Caddy’s local CA; :80 serves plain HTTP behind another proxy.
OPENDESK_URL https://localhost Public URL. Sets API_URL, APP_URL, CORS_ORIGINS and SITE_URL.
OPENDESK_GATEWAY_URL wss://localhost/socket Public WebSocket URL (GATEWAY_URL).
CADDY_GLOBAL_OPTIONS empty Caddy global options, such as email you@example.com for the ACME account.
HTTP_PORT, HTTPS_PORT 80, 443 Host ports Caddy publishes.
OPENDESK_CADDYFILE ./deploy/caddy/Caddyfile The Caddyfile to mount.
CHECK_ORIGINS OPENDESK_URL Browser origins the gateway accepts for agent sockets.
POSTGRES_PASSWORD required Password for the bundled Postgres.
SECRET_KEY_BASE required The gateway’s Phoenix secret.
NATS_TOKEN required Authenticates the gateway to the embedded NATS.
INBOUND_SECRET required Authenticates POST /v1/inbound/email.
COMPOSE_PROFILES empty Optional services, comma separated: minio, smtp-inbound, ollama.
WEB_POSTHOG_KEY, WEB_POSTHOG_HOST empty Browser-side analytics for the web app. Empty sends nothing.
OPENDESK_DATA, OPENDESK_PG_DATA, OPENDESK_S3_DATA, OPENDESK_OLLAMA_DATA named volumes Override where data is stored, for example with a host path.

Inside the bundle, opendesk always runs with OPENDESK_ENV=production, OPENDESK_ROLE=all and TRUST_PROXY=true (Caddy is the only way in), and DATABASE_URL points at the bundled Postgres. Every other application setting is passed through from .env.

Optional services are compose profiles. Turn them on with COMPOSE_PROFILES in .env, or --profile on the command line.

S3-compatible attachment storage on the same machine, instead of the opendesk-data volume. It runs RustFS, which speaks the same S3 API, and an s3-init job creates the bucket once.

Terminal window
COMPOSE_PROFILES=minio
STORAGE_DRIVER=s3
S3_ENDPOINT=http://s3:9000
S3_FORCE_PATH_STYLE=true
S3_BUCKET=opendesk
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=opendesk
S3_SECRET_ACCESS_KEY=<a long random value>

It refuses to start without S3_SECRET_ACCESS_KEY. Attachment downloads are presigned URLs, so browsers must be able to reach S3_ENDPOINT too.

Publishes opendesk’s built-in SMTP receiver on the host, so an MX record for INBOUND_DOMAIN can point at this machine. A small TCP relay keeps opendesk itself unpublished.

Terminal window
COMPOSE_PROFILES=smtp-inbound
INBOUND_SMTP_ADDR=:2525
INBOUND_SMTP_PORT=25 # host port, default 25

See Email setup.

Fully local AI through Ollama’s OpenAI-compatible API. Nothing leaves the machine.

Terminal window
COMPOSE_PROFILES=ollama
AI_PROVIDER=openai
OPENAI_BASE_URL=http://ollama:11434/v1
OPENAI_API_KEY=ollama
AI_MODEL=llama3.1:8b
AI_FAST_MODEL=llama3.1:8b
EMBEDDINGS_DRIVER=openai
EMBEDDINGS_MODEL=nomic-embed-text

An ollama-init job pulls AI_MODEL, AI_FAST_MODEL and EMBEDDINGS_MODEL once into the ollama-data volume. CPU is enough for small models; a GPU needs the host’s NVIDIA container toolkit. The installer’s --ollama flag sets all of this. See Self-hosting AI.

Combine profiles with commas: COMPOSE_PROFILES=minio,ollama.

Path Goes to
/socket, /socket/* realtime:4000, including the chat widget socket at /socket/widget
everything else opendesk:8080: the API, web app, widget script, help center, /config.js and /config.json

Caddy’s access log strips the token query parameter and the Authorization and Cookie headers, since realtime tokens travel in ?token=.

Volume Holds
opendesk-data Attachments and raw email (local storage), embedded NATS data
postgres-data The database
caddy-data, caddy-config Certificates and Caddy state
s3-data The minio profile’s objects
ollama-data The ollama profile’s models
Terminal window
docker compose ps
docker compose logs -f opendesk
docker compose restart opendesk
docker compose down # stop, keep data

Migrations run automatically when opendesk starts. For backups and upgrades, use opendesk-ctl.

Edit this page on GitHub