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
- Open
http://your-server:8080in a browser - 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
- Create your first agent accounts under Admin → Manage → Users
| Default Access | URL |
|---|---|
| Web UI | http://your-server:8080 |
| Admin panel | http://your-server:8080/#manage |
| API | http://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.
Full-Text Search
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
Slow Full-Text Search
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
| Component | RAM Usage |
|---|---|
| Rails server | ~500 MB |
| WebSocket server | ~500 MB |
| Scheduler | ~500 MB |
| Nginx | ~100 MB |
| PostgreSQL | ~500 MB |
| Redis | ~100 MB |
| Memcached | 256 MB |
| Elasticsearch | 1-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.
Related
- Zammad Elasticsearch Errors: Fix Guide
- Zammad vs Freshdesk: Self-Hosted or SaaS Helpdesk?
- Zammad vs OTOBO: Which Helpdesk to Self-Host?
- Best Self-Hosted Ticketing & Helpdesk
- FreeScout vs Zammad
- OsTicket vs Zammad
- Replace Zendesk with Self-Hosted Alternatives
- Docker Compose Basics
- Reverse Proxy Setup
- Backup Strategy
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments