Cariosan

Docker Compose

What the production compose stack deploys and how to customise it.

5 min readUpdated Mar 12, 2026

The docker-compose.yml we publish is the reference deployment. It boots Cariosan plus every service it depends on, with sensible healthchecks and depends_on gates so docker compose up -d waits until the stack is actually ready.

What it runs

ServiceImagePurpose
cariosan-serverghcr.io/cariosan/server:latestThe Go binary. Applies migrations on boot.
postgrespostgres:15-alpineMessages, users, channels, webhooks.
redisredis:7-alpineRate limiting, presence, pub/sub fanout.
miniominio/minio:latestS3-compatible store for attachments.
minio-bootstrapminio/mc:latestOne-shot: creates the bucket with a public-read policy, then exits.

All services live on an isolated cariosan network. Only the Cariosan HTTP port (:8080) and the MinIO S3 API (:9000, needed so browsers can reach presigned URLs) are published to the host.

Why MinIO is exposed — browsers PUT attachments directly to S3 via presigned URLs (the bytes never proxy through Cariosan). The MinIO endpoint must be reachable from end users for this to work.

Pin a specific version

The default CARIOSAN_IMAGE=ghcr.io/cariosan/server:latest floats on the most recent release.

.env
CARIOSAN_IMAGE=ghcr.io/cariosan/server:v0.1.0

Pin in production:latest is convenient for staging but a surprise in prod. Tag a specific semver so rollbacks (docker compose pull && up -d) are deterministic.

See the operations guide for the upgrade and rollback loop.

Using an external Postgres or S3

Most teams eventually run managed Postgres (RDS, Neon, Supabase) instead of the bundled container. Delete the postgres service from compose and point CARIOS_DATABASE_URL at your managed instance:

.env
CARIOS_DATABASE_URL=postgres://user:pass@pg-prod.internal:5432/cariosan?sslmode=require

For S3 → AWS S3 (or any S3-compatible service), remove the minio and minio-bootstrap services and supply credentials:

.env
CARIOS_S3_ENDPOINT=
CARIOS_S3_ACCESS_KEY=AKIA...
CARIOS_S3_SECRET_KEY=...
CARIOS_S3_BUCKET=cariosan-prod
CARIOS_S3_REGION=ap-southeast-1

CARIOS_S3_ENDPOINT= (empty) tells the AWS SDK to use the default region endpoint. The IAM user needs s3:PutObject + s3:GetObject on the bucket.

Resource sizing

The server is stateless and light: idle RSS is under 40 MB, and a single CPU comfortably handles up to a few thousand connected WebSockets. Bottlenecks are Postgres and Redis first.

For a VPS-scale deployment, start with:

  • Server: 0.5 vCPU / 512 MB RAM
  • Postgres: 1 vCPU / 1 GB RAM, 20 GB SSD
  • Redis: 0.25 vCPU / 256 MB RAM
  • MinIO: 0.25 vCPU / 256 MB RAM, 10+ GB disk (grows with attachments)

Scale horizontally — run multiple cariosan-server containers behind a sticky-session-free load balancer. Redis pub/sub handles the cross-instance WebSocket fanout. See performance & scaling for sizing tiers.

TLS

Cariosan speaks plain HTTP on purpose — terminate TLS at whatever reverse proxy you already trust (Caddy, nginx, Traefik, a cloud load balancer).

chat.example.com {
    reverse_proxy cariosan-server:8080
}

WebSocket upgrade — browsers need wss:// for WebSockets, so the reverse proxy must pass the upgrade handshake. Caddy does this by default; nginx requires the Upgrade/Connection headers above. If clients fail to connect with 101 errors, the proxy is the first place to check.

Was this page helpful?

On this page