Self-Hosting Zammad with Docker Compose

What Is Zammad?

Zammad is a web-based, open-source helpdesk and ticketing system that handles customer support across email, phone, chat, Twitter, and Facebook — all in one interface. It replaces Zendesk, Freshdesk, and Help Scout with a self-hosted solution that gives you full control over your support data. Zammad’s standout feature is its powerful full-text search powered by Elasticsearch, which indexes every ticket, email, and attachment for instant retrieval.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 10 GB of RAM (6 GB minimum without Elasticsearch)
  • 2 CPU cores minimum, 4 recommended
  • 30 GB of free disk space
  • A domain name (for email integration and web access)

Docker Compose Configuration

Zammad runs as a multi-container stack with separate services for the web app, WebSocket connections, background jobs, Nginx reverse proxy, PostgreSQL, Redis, Memcached, and optionally Elasticsearch.

Create a docker-compose.yml file:

services:
  zammad-init:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: on-failure
    command: ["zammad-init"]
    depends_on:
      - zammad-postgresql
    volumes:
      - zammad-storage:/opt/zammad/storage
    environment:
      - POSTGRESQL_HOST=zammad-postgresql
      - POSTGRESQL_PORT=5432
      - POSTGRESQL_DB=zammad_production
      - POSTGRESQL_USER=zammad
      - POSTGRESQL_PASS=${POSTGRES_PASS}
      - POSTGRESQL_OPTIONS=?pool=50
      - MEMCACHE_SERVERS=zammad-memcached:11211
      - REDIS_URL=redis://zammad-redis:6379
      - ELASTICSEARCH_ENABLED=${ELASTICSEARCH_ENABLED:-true}
      - ELASTICSEARCH_HOST=zammad-elasticsearch
      - ELASTICSEARCH_PORT=9200
      - ELASTICSEARCH_SCHEMA=http
      - ELASTICSEARCH_NAMESPACE=zammad
      - ELASTICSEARCH_REINDEX=true
    networks:
      - zammad

  zammad-railsserver:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: unless-stopped
    command: ["zammad-railsserver"]
    depends_on:
      - zammad-init
      - zammad-memcached
      - zammad-postgresql
      - zammad-redis
    volumes:
      - zammad-storage:/opt/zammad/storage
    environment:
      - POSTGRESQL_HOST=zammad-postgresql
      - POSTGRESQL_PORT=5432
      - POSTGRESQL_DB=zammad_production
      - POSTGRESQL_USER=zammad
      - POSTGRESQL_PASS=${POSTGRES_PASS}
      - POSTGRESQL_OPTIONS=?pool=50
      - MEMCACHE_SERVERS=zammad-memcached:11211
      - REDIS_URL=redis://zammad-redis:6379
      - ELASTICSEARCH_ENABLED=${ELASTICSEARCH_ENABLED:-true}
      - ELASTICSEARCH_HOST=zammad-elasticsearch
      - ELASTICSEARCH_PORT=9200
      - ELASTICSEARCH_SCHEMA=http
      - ELASTICSEARCH_NAMESPACE=zammad
    networks:
      - zammad

  zammad-websocket:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: unless-stopped
    command: ["zammad-websocket"]
    depends_on:
      - zammad-init
      - zammad-memcached
      - zammad-postgresql
      - zammad-redis
    volumes:
      - zammad-storage:/opt/zammad/storage
    environment:
      - POSTGRESQL_HOST=zammad-postgresql
      - POSTGRESQL_PORT=5432
      - POSTGRESQL_DB=zammad_production
      - POSTGRESQL_USER=zammad
      - POSTGRESQL_PASS=${POSTGRES_PASS}
      - POSTGRESQL_OPTIONS=?pool=50
      - MEMCACHE_SERVERS=zammad-memcached:11211
      - REDIS_URL=redis://zammad-redis:6379
    networks:
      - zammad

  zammad-scheduler:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: unless-stopped
    command: ["zammad-scheduler"]
    depends_on:
      - zammad-init
      - zammad-memcached
      - zammad-postgresql
      - zammad-redis
    volumes:
      - zammad-storage:/opt/zammad/storage
    environment:
      - POSTGRESQL_HOST=zammad-postgresql
      - POSTGRESQL_PORT=5432
      - POSTGRESQL_DB=zammad_production
      - POSTGRESQL_USER=zammad
      - POSTGRESQL_PASS=${POSTGRES_PASS}
      - POSTGRESQL_OPTIONS=?pool=50
      - MEMCACHE_SERVERS=zammad-memcached:11211
      - REDIS_URL=redis://zammad-redis:6379
    networks:
      - zammad

  zammad-nginx:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: unless-stopped
    command: ["zammad-nginx"]
    ports:
      - "8080:8080"
    depends_on:
      - zammad-railsserver
    environment:
      - NGINX_SERVER_NAME=_
      - NGINX_SERVER_SCHEME=${NGINX_SERVER_SCHEME:-http}
      - NGINX_CLIENT_MAX_BODY_SIZE=50M
    networks:
      - zammad

  zammad-postgresql:
    image: postgres:17.2-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=zammad_production
      - POSTGRES_USER=zammad
      - POSTGRES_PASSWORD=${POSTGRES_PASS}
    volumes:
      - postgresql-data:/var/lib/postgresql/data
    networks:
      - zammad

  zammad-memcached:
    image: memcached:1.6.34-alpine
    restart: unless-stopped
    command: memcached -m 256
    networks:
      - zammad

  zammad-redis:
    image: redis:7.4.2-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - zammad

  zammad-elasticsearch:
    image: elasticsearch:8.17.0
    restart: unless-stopped
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    networks:
      - zammad

volumes:
  zammad-storage:
  postgresql-data:
  redis-data:
  elasticsearch-data:

networks:
  zammad:
    driver: bridge

Create a .env file alongside:

# PostgreSQL password — change this to a strong random value
POSTGRES_PASS=CHANGE_ME_USE_STRONG_PASSWORD

# Elasticsearch — set to false to disable (saves ~4 GB RAM)
ELASTICSEARCH_ENABLED=true

# Reverse proxy scheme — set to https if behind SSL-terminating proxy
NGINX_SERVER_SCHEME=http

Before starting, set the required kernel parameter for Elasticsearch:

sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

Start the stack:

docker compose up -d

The first startup takes 2-3 minutes while zammad-init runs database migrations.

Initial Setup

  1. Open http://your-server:8080 in a browser
  2. Zammad’s setup wizard walks you through:
    • Organization name and admin account creation
    • Email channel setup — connect your support email (IMAP/POP3 for receiving, SMTP for sending)
    • Base URL — set to your public domain
  3. Create your first agent accounts under Admin → Manage → Users
Default AccessURL
Web UIhttp://your-server:8080
Admin panelhttp://your-server:8080/#manage
APIhttp://your-server:8080/api/v1/

Configuration

Email Channels

Zammad supports multiple email channels. Each channel connects to a mailbox (via IMAP or POP3) and sends replies via SMTP. Configure under Admin → Channels → Email:

  • Inbound: IMAP server, credentials, folder to watch
  • Outbound: SMTP server, port, authentication
  • Routing: Which group handles emails from which address

Ticket Automation

Zammad’s trigger system automates common workflows:

  • Auto-assign tickets based on keywords
  • Send auto-replies to customers
  • Escalate tickets after a time threshold
  • Close tickets after inactivity

Configure under Admin → Manage → Trigger.

With Elasticsearch enabled, Zammad indexes all tickets, articles, attachments, and user data. Search across everything from the global search bar. If you disabled Elasticsearch to save RAM, Zammad falls back to PostgreSQL full-text search (slower but functional).

Reverse Proxy

Place Zammad behind a reverse proxy for SSL termination. With Nginx Proxy Manager, proxy your domain to localhost:8080 and enable WebSocket support.

When using a reverse proxy, update the .env:

NGINX_SERVER_SCHEME=https

For more details, see our Reverse Proxy Setup guide.

Backup

Zammad includes a built-in backup service. Add to your Compose file:

  zammad-backup:
    image: ghcr.io/zammad/zammad:7.0.0
    restart: unless-stopped
    command: ["zammad-backup"]
    depends_on:
      - zammad-railsserver
      - zammad-postgresql
    volumes:
      - zammad-storage:/opt/zammad/storage
      - zammad-backup:/var/tmp/zammad
    environment:
      - BACKUP_TIME=03:00
      - HOLD_DAYS=10
      - POSTGRESQL_HOST=zammad-postgresql
      - POSTGRESQL_PORT=5432
      - POSTGRESQL_DB=zammad_production
      - POSTGRESQL_USER=zammad
      - POSTGRESQL_PASS=${POSTGRES_PASS}
    networks:
      - zammad

Add zammad-backup: to your volumes section. Backups run daily at 03:00 UTC and retain 10 days of history.

For a comprehensive backup strategy, see our Backup Strategy guide.

Troubleshooting

Elasticsearch Fails to Start (Exit Code 78)

Symptom: The zammad-elasticsearch container exits immediately with code 78.

Fix: Set the vm.max_map_count kernel parameter:

sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

Symptom: Searches take several seconds, especially across large ticket volumes.

Fix: Verify Elasticsearch is running and connected. Rebuild the search index:

docker compose exec zammad-railsserver rails r "SearchIndexBackend.create_index"
docker compose exec zammad-railsserver rails r "SearchIndexBackend.rebuild_index"

Email Not Being Fetched

Symptom: Tickets aren’t being created from incoming emails.

Fix: Check channel settings under Admin → Channels → Email. Verify IMAP credentials and that the mailbox folder is correct. Check the scheduler logs:

docker compose logs zammad-scheduler

High Memory Usage

Symptom: Server runs out of RAM with the full stack.

Fix: Elasticsearch consumes the most RAM (1 GB heap by default). Reduce it:

environment:
  - ES_JAVA_OPTS=-Xms512m -Xmx512m

Or disable Elasticsearch entirely by setting ELASTICSEARCH_ENABLED=false — Zammad falls back to PostgreSQL search.

zammad-init Takes Forever

Symptom: The init container runs for more than 10 minutes.

Fix: On first startup, database migration can take 3-5 minutes. If it exceeds 10 minutes, check logs:

docker compose logs zammad-init

Common cause: PostgreSQL is not ready. Ensure the database container is healthy before init runs.

Resource Requirements

  • RAM: 6 GB minimum (without Elasticsearch), 10 GB recommended (with Elasticsearch)
  • CPU: 2 cores minimum, 4 cores for 10+ agents
  • Disk: 10 GB for the application, plus storage for ticket attachments and database growth
ComponentRAM Usage
Rails server~500 MB
WebSocket server~500 MB
Scheduler~500 MB
Nginx~100 MB
PostgreSQL~500 MB
Redis~100 MB
Memcached256 MB
Elasticsearch1-2 GB

Verdict

Zammad is the best self-hosted helpdesk for teams that need professional multi-channel support. Its Elasticsearch-powered search is genuinely excellent — finding a ticket from two years ago by a vague keyword takes seconds. The email integration is solid, the web UI is modern, and the trigger system handles most automation needs. The tradeoff is resource usage: the full stack needs 10 GB of RAM with Elasticsearch. For solo operators or small teams who just need email-based ticketing, FreeScout is lighter. For teams of 5+ agents handling support across email, chat, and social, Zammad is the clear winner.

Comments