Deploy on a VM

Run the production stack on one virtual machine with Docker Compose, Postgres, Qdrant and Caddy for TLS.

After reading this page you will be able to:
  • Prepare deploy/.env with the required secrets
  • Start the stack with Docker Compose
  • Put Caddy in front for TLS and edge rate limits

Prerequisites

  • A Linux VM with Docker Engine and the Compose plugin. 4 vCPU and 16 GB RAM is a comfortable start.
  • A DNS name pointing at the VM if you want a real certificate. Without one, deploy/vm/push.sh uses <ip-with-dashes>.sslip.io (for example 203-0-113-7.sslip.io).
  • Ports 80 and 443 open to the internet. Nothing else needs to be.

What runs

ServiceImageExposedPurpose
postgrespostgres:16-alpineNot on the hostSystem of record (TICKETIQ_DB_URL)
qdrantqdrant/qdrant:v1.15.5127.0.0.1:6341 onlyVector store for tickets and knowledge
ticketiqBuilt from deploy/Dockerfile127.0.0.1:8787The API and console, with health check /api/health
caddyBuilt from deploy/caddy/Dockerfile80 and 443TLS, edge rate limits (VM stack)
mcpSame build127.0.0.1:8000Optional MCP server, only with --profile mcp. Needs TICKETIQ_MCP_TOKEN and TICKETIQ_API_KEY

Steps

  1. Copy the example environment file and fill it in.
    cp deploy/.env.example deploy/.env
    VariableWhat to set
    PG_PASSWORDA long random string. It is fixed at first start, so don't change it afterwards.
    QDRANT_API_KEYA long random string.
    TICKETIQ_ADMIN_PASSWORDThe first admin's password. Required on a non-loopback address.
    TICKETIQ_PUBLIC_BASE_URLNot in the example file: add it. The external https:// address. SSO and per-user account connections refuse to start without it, and the Teams package uses it.
    TICKETIQ_BRAND_NAMEOptional: the name shown in the console.
  2. Build and start the stack.
    docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
  3. Check health: curl -s http://127.0.0.1:8787/api/health.
  4. For the VM layout with Caddy and a local model server, use deploy/vm/docker-compose.vm.yml and the Caddyfile beside it. deploy/vm/push.sh <vm-ip> [domain] ships the image and config to a VM over SSH, and deploy/vm/update-app.sh rolls a new build.
Note

The Caddy image includes the pinned caddy-ratelimit module. A stock Caddy image refuses to start on the rate_limit directive in the Caddyfile. Caddy enforces TLS 1.2 or later, per-address request windows and tighter limits on sign-in endpoints.

Use an external Postgres

Add the overlay and point TICKETIQ_DB_URL at your database, with ?sslmode=verify-full:

docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.external-db.yml up -d
Warning

Back up pgdata before upgrades. deploy/backup.sh runs pg_dump through docker compose exec, so the database never needs a host port.

Next steps