jChat LogojChat Docs
Production Deployment

Docker & Compose Deployment

Deploy JChat using the pre-bundled docker-compose.yml or build a custom container image from source.

Overview

Every JChat release package comes pre-bundled with a production-ready docker-compose.yml, Dockerfile, and Caddyfile. Customers do not need to construct a Docker Compose stack from scratch—simply extract the release archive and launch.

Choosing Your Deployment Mode

Choose the approach that fits your operational requirements:

1. Standard Deployment (Using Personalized or Bundled docker-compose.yml)

Recommended for standard production use. You can download a personalized docker-compose.yml directly from your JChat Store Dashboard (under Purchases > Docker Setup (.yml)) with your license key pre-configured, or use the bundled file.

Uses the official pre-built image (ghcr.io/jchatcloud/jchat:latest). No local building or compilation needed. Runs JChat, PostgreSQL 18, Redis, and Caddy automatically.

docker compose up -d

2. Custom Code Changes (Building Your Own Image)

If you customize JChat's source code, components, or API features, build your custom container directly using the included Dockerfile with the --build flag. The bundled docker-compose.yml automatically handles building from source.

docker compose up --build -d

Pre-Bundled docker-compose.yml

Below is the exact production Compose stack included in your release package:

services:
  jchat:
    image: ghcr.io/jchatcloud/jchat:latest
    build:
      context: .
      dockerfile: Dockerfile
    container_name: jchat-app
    restart: unless-stopped
    environment:
      NODE_ENV: production
      JCHAT_PORT: "3000"
      JCHAT_INTERNAL_DB_HOST: db
      JCHAT_INTERNAL_REDIS_HOST: redis
      JCHAT_ENV_PATH: /app/config/.env
    volumes:
      - ./config:/app/config
      - ./data:/app/data
      - ./addons:/app/addons
      - uploads:/app/uploads
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - jchat-network

  db:
    image: postgres:18-alpine
    container_name: jchat-db
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-jchat}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-jchat_secret}
      POSTGRES_DB: ${POSTGRES_DB:-jchat}
    ports:
      - "5434:5432"
    volumes:
      - postgres_data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-jchat}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - jchat-network

  redis:
    image: redis:8.0.6-alpine3.21
    container_name: jchat-redis
    restart: always
    ports:
      - "6380:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - jchat-network

  caddy:
    image: caddy:alpine
    container_name: caddy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - jchat
    networks:
      - jchat-network

  minio:
    image: minio/minio:latest
    profiles: ["minio"]
    container_name: jchat-minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
    volumes:
      - minio_data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - jchat-network

  minio-init:
    image: minio/mc:latest
    profiles: ["minio"]
    container_name: jchat-minio-init
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
      mc alias set local http://minio:9000 $${MINIO_ROOT_USER:-minioadmin} $${MINIO_ROOT_PASSWORD:-minioadmin};
      mc mb --ignore-existing local/$${JCHAT_MINIO_BUCKET_NAME:-jchat};
      mc anonymous set public local/$${JCHAT_MINIO_BUCKET_NAME:-jchat};
      echo 'MinIO bucket ready.';
      "
    networks:
      - jchat-network

networks:
  jchat-network:
    driver: bridge

volumes:
  postgres_data:
  redis_data:
  uploads:
  minio_data:
  caddy_data:
  caddy_config:

Starting and Managing the Stack

Essential operational commands for your Docker deployment:

# Start using the official pre-built image:
docker compose up -d

# View live application logs:
docker compose logs -f jchat
# Build and launch with your custom source modifications:
docker compose up --build -d

# Check build & application status:
docker compose ps
# Stop the stack safely:
docker compose down

# Restart containers after config changes:
docker compose restart jchat

Using Nginx Instead of Caddy

While Caddy is bundled by default for zero-configuration automatic SSL, you can easily substitute Nginx if required by your infrastructure standards:

1. Replace Caddy in docker-compose.yml

In your docker-compose.yml, replace the caddy service with an official nginx:alpine container. Notice the ./nginx.conf mount: this reads the configuration directly from your root project folder:

nginx:
  image: nginx:alpine
  container_name: jchat-nginx
  restart: always
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    - /etc/letsencrypt:/etc/letsencrypt:ro
  depends_on:
    - jchat
  networks:
    - jchat-network

2. Create nginx.conf in the Root Directory

Create an nginx.conf file in the root deployment directory directly alongside docker-compose.yml (e.g., at /path/to/jchat/nginx.conf). Ensure WebSocket upgrade headers, client IP forwarding, and file upload limits (50MB) are included:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name chat.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name chat.example.com;

    ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 50M;

    location / {
        proxy_pass http://jchat:3000;
        proxy_http_version 1.1;

        # Real-time WebSocket Upgrades
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Standard Forwarding Headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Keep persistent WebSocket connections alive
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

3. Launch Containers with Nginx

Start the stack with your new Nginx configuration active:

docker compose up -d

On this page