Production deployment with Docker Compose

中文版

This page deploys the IMCore Basic edition from zero onto a public VPS: Docker Compose brings up PostgreSQL + Redis + the server, while Caddy terminates TLS with automatically issued certificates and proxies WebSocket. About 15 minutes end to end.

Just want to kick the tires locally? Use the one-command setup in the Quickstart (placeholder secrets — never expose it publicly). Compose, Kubernetes and systemd (see the release README) are the three supported deployment forms — pick one per environment.

Prerequisites

  • A Linux server (2 vCPU / 4 GB minimum; measured capacity of a 4C8G single instance is in Self-hosting a chat server)
  • A domain name with an A record pointing at the server (examples use im.example.com — replace with yours)
  • Docker Engine 24+ and Docker Compose v2.23+ (check with docker compose version)
  • Ports 80 / 443 open (80 for certificate issuance, 443 for traffic)

1. Directory and secrets

mkdir -p /opt/imcore && cd /opt/imcore

Generate two secrets and keep them (both are used in the next two steps):

openssl rand -base64 24   # POSTGRES_PASSWORD
openssl rand -base64 32   # JWT_SECRET (for minting client tokens)

2. Server configuration — app.yaml

Create /opt/imcore/app.yaml. Compared with the local demo config, exactly three things change: placeholder secrets become the generated values, URLs switch to your domain (https/wss), and demo mode is disabled:

app:
  logFile: log/gin.log
  httpPort: 8080
  standalone_websocket_enabled: false
  webSocketPort: 8089
  rpcPort: 9001
  httpUrl: https://im.example.com
  webSocketUrl: wss://im.example.com/acc
  autoOpenBrowser: false

redis:
  addr: "redis:6379"
  username: ""
  password: ""
  DB: 0
  poolSize: 50
  minIdleConns: 10

database:
  driver: "postgres"

postgres:
  dsn: "postgres://imcore:<POSTGRES_PASSWORD>@postgres:5432/imcore?sslmode=disable&connect_timeout=5"
  im:
    dsn: "postgres://imcore:<POSTGRES_PASSWORD>@postgres:5432/imcore?sslmode=disable&connect_timeout=5"

auth:
  jwt_secret: "<JWT_SECRET>"
  jwt_issuer: "imcore-prod"
  jwt_audience: "gowebsocket-client"
  jwt_required: true
  allow_empty_origin: false

demo:
  product_mode: false
  guest_token_enabled: false
  seed:
    enabled: false

broker:
  type: ""
rabbitmq:
  enabled: false

Notes:

  • Replace <POSTGRES_PASSWORD> in both postgres.dsn entries with the database password from step 1.
  • demo.* is fully disabled — guest login and the seeded demo group belong to the local experience config only.
  • The Basic edition is single-instance and needs no broker; clustered topologies (Pro and up) are covered in the last section.
  • Paid editions additionally place the license file on the server and point license.file at it (not needed for Basic).

3. docker-compose.yml

Create /opt/imcore/docker-compose.yml:

name: imcore

services:
  postgres:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_USER: imcore
      POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
      POSTGRES_DB: imcore
    volumes:
      - pgdata:/var/lib/postgresql/data
    configs:
      - source: initdb_vector
        target: /docker-entrypoint-initdb.d/01-vector.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U imcore -d imcore"]
      interval: 5s
      timeout: 5s
      retries: 12
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 12
    restart: unless-stopped

  # One-shot: applies pending SQL migrations, then exits.
  # Re-runs automatically on `docker compose up -d` after an upgrade.
  migrate:
    image: ${IMCORE_IMAGE:-crpi-zegbek7xpkekqods.cn-hangzhou.personal.cr.aliyuncs.im/balala/imcore-basic:latest}
    environment:
      APP_BIN: migrate
    configs:
      - source: app_yaml
        target: /app/config/app.yaml
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"

  imcore:
    image: ${IMCORE_IMAGE:-crpi-zegbek7xpkekqods.cn-hangzhou.personal.cr.aliyuncs.im/balala/imcore-basic:latest}
    configs:
      - source: app_yaml
        target: /app/config/app.yaml
    depends_on:
      migrate:
        condition: service_completed_successfully
      redis:
        condition: service_healthy
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - imcore
    restart: unless-stopped

configs:
  app_yaml:
    file: ./app.yaml
  initdb_vector:
    content: |
      CREATE EXTENSION IF NOT EXISTS vector;

volumes:
  pgdata:
  caddy_data:
  caddy_config:

Notes:

  • The default image comes from Aliyun ACR (stable pulls inside China). For servers elsewhere use Docker Hub: IMCORE_IMAGE=balalaim/imcore-basic:latest docker compose up -d.
  • imcore publishes no ports — only Caddy reaches it over the compose network; the same goes for PostgreSQL and Redis.
  • initdb_vector enables the pgvector extension at first database initialization (required by the vector tables of the CS edition’s RAG features); it runs only on an empty data dir.
  • Air-gapped networks: docker pull all images on a connected machine, docker save, transfer, docker load.

4. Caddyfile (automatic HTTPS)

Create /opt/imcore/Caddyfile with your domain:

im.example.com {
	encode gzip zstd
	reverse_proxy imcore:8080
}

Caddy obtains and renews Let’s Encrypt certificates automatically; WebSocket upgrades pass through reverse_proxy natively, so /acc needs no extra configuration.

5. Start and verify

docker compose up -d
docker compose ps        # postgres/redis/imcore/caddy Up, migrate Exited(0)
docker compose logs migrate   # confirm all migrations applied

Open https://im.example.com in a browser — if the site loads, the server is up. Clients follow the client integration guide: your backend mints JWTs and connects to wss://im.example.com/acc.

Upgrades and backups

# Upgrade: pull the new image and re-up (migrate applies the new version's migrations)
docker compose pull
docker compose up -d

# Database backup (daily cron recommended)
docker compose exec postgres pg_dump -U imcore imcore > imcore-$(date +%F).sql

Rollback: switch back to the previous image tag and up -d again (see the release README for migration compatibility policy).

When you need more components

Need Add
Horizontal scaling (Pro and up) A RabbitMQ / NATS / Redis Streams broker + persist_consumer; all replicas share one Redis and one broker
Orchestration, HPA, rolling upgrades Kubernetes (Helm)
AI support / RAG knowledge base CS edition image + PostgreSQL (pgvector — already in place here)
Preference for systemd on a single host The systemd path in the release README

For selection background and capacity numbers see Self-hosting a chat server; for head-to-head comparisons against SaaS and open-source options see the comparison pages.