Zammad ist eine leistungsstarke Open-Source-Helpdesk-Software, die eine vereinheitlichte Plattform für die Verwaltung von Kundenanfragen bietet, die aus verschiedenen Kommunikationskanälen wie E-Mail, sozialen Medien und Telefonanrufen stammen. Sie bietet eine klare und intuitive Benutzeroberfläche, die den Support-Teams die Möglichkeit gibt, Tickets zu erstellen, zuzuweisen und zu verfolgen.
Zusätzlich zur Ticketverwaltung verfügt Zammad über eine Reihe von Features, die das Kundenbeziehungsmanagement (CRM) unterstützen, eine Wissensdatenbank zur Organisation und Speicherung relevanter Informationen bereitstellen und verschiedene Automatisierungsoptionen ermöglichen. Zammad kann entweder auf einem privaten Server installiert oder als gehosteter Service genutzt werden.
0. Versionierung
Datum | Änderung |
---|---|
18.05.2025 | Update der Anleitung an die aktuelle Zammad + Traefik Version (Update durch @christian) |
26.07.2023 | Update der .env auf die aktuellste Version. Bitte in eurem Setup übernehmen und dann ein docker compose pull && docker compose up -d durchführen |
16.07.2023 | Initialer Release |
1. Zielsetzung
Unser Ziel in dieser Anleitung ist es, den Installationsprozess von Zammad in einem Docker-Container auf einem Server zu erleichtern und zu erklären, wie man Traefik als Reverse Proxy konfiguriert.
2. Voraussetzung
Bevor wir mit der Installation und Konfiguration von Zammad beginnen, müssen wir einige Voraussetzungen erfüllen. Die wichtigste davon ist, dass wir Traefik V3 Installation, Konfiguration und CrowdSec-Security installiert haben.
Für Zammad ist mindestens empfohlen:
- 2 CPU-Kerne
- 4 GB RAM (+4 GB, wenn wir Elasticsearch auf demselben Server ausführen möchten)
Natürlich hängt dies letztendlich von der tatsächlichen Last durch gleichzeitige Agenten und Datenverkehr ab.
3. Verzeichnisse / Dateien anlegen
Um mit der Installation und Konfiguration von Zammad zu beginnen, müssen wir zuerst die notwendigen Verzeichnisse und Dateien erstellen. Dazu erstellen wir ein Hauptverzeichnis für Zammad und verschiedene Dateien, die wir im Verlauf dieser Anleitung benötigen.
Führen Sie die folgenden Befehle in Ihrer Linux-Shell aus, um das Verzeichnis und die Dateien zu erstellen:
# Hier erstellen wir das Hauptverzeichnis und ein Unterverzeichnis für Zammad mkdir -p /opt/containers/zammad
Sie dienen als Grundlage für die Installation und Konfiguration von Zammad in den nachfolgenden Schritten.
4. Erstellung der docker-compose.yml
4.1. Original Datei von Github
Die docker-compose.yml
Datei ist das Herzstück unserer Docker Konfiguration. Sie definiert, welche Dienste gestartet werden und wie sie miteinander interagieren. Um diese Datei zu erstellen, nehmen wir uns die Vorlage aus der offiziellen Zammad-Dokumentation als Basis, welche unter folgendem Link zu finden ist: https://docs.zammad.org/en/latest/install/docker-compose.html.
Die docker-compose.yml
Vorlage befindet sich im Zammad Github-Repository: https://github.com/zammad/zammad-docker-compose. Wir kopieren den Inhalt dieser Datei in unsere neu erstellte docker-compose.yml
:
nano /opt/containers/zammad/docker-compose.yml
In die geöffnete Datei kopieren wir folgenden Inhalt:
x-shared: zammad-service: &zammad-service environment: &zammad-environment MEMCACHE_SERVERS: ${MEMCACHE_SERVERS:-zammad-memcached:11211} POSTGRESQL_DB: ${POSTGRES_DB:-zammad_production} POSTGRESQL_HOST: ${POSTGRES_HOST:-zammad-postgresql} POSTGRESQL_USER: ${POSTGRES_USER:-zammad} POSTGRESQL_PASS: ${POSTGRES_PASS:-zammad} POSTGRESQL_PORT: ${POSTGRES_PORT:-5432} POSTGRESQL_OPTIONS: ${POSTGRESQL_OPTIONS:-?pool=50} POSTGRESQL_DB_CREATE: REDIS_URL: ${REDIS_URL:-redis://zammad-redis:6379} S3_URL: # Backup settings BACKUP_DIR: "${BACKUP_DIR:-/var/tmp/zammad}" BACKUP_TIME: "${BACKUP_TIME:-03:00}" HOLD_DAYS: "${HOLD_DAYS:-10}" TZ: "${TZ:-Europe/Berlin}" # Allow passing in these variables via .env: AUTOWIZARD_JSON: AUTOWIZARD_RELATIVE_PATH: ELASTICSEARCH_ENABLED: ELASTICSEARCH_SCHEMA: ELASTICSEARCH_HOST: ELASTICSEARCH_PORT: ELASTICSEARCH_USER: ${ELASTICSEARCH_USER:-elastic} ELASTICSEARCH_PASS: ${ELASTICSEARCH_PASS:-zammad} ELASTICSEARCH_NAMESPACE: ELASTICSEARCH_REINDEX: NGINX_PORT: NGINX_CLIENT_MAX_BODY_SIZE: NGINX_SERVER_NAME: NGINX_SERVER_SCHEME: RAILS_TRUSTED_PROXIES: ZAMMAD_HTTP_TYPE: ZAMMAD_FQDN: ZAMMAD_WEB_CONCURRENCY: ZAMMAD_PROCESS_SESSIONS_JOBS_WORKERS: ZAMMAD_PROCESS_SCHEDULED_JOBS_WORKERS: ZAMMAD_PROCESS_DELAYED_JOBS_WORKERS: # ZAMMAD_SESSION_JOBS_CONCURRENT is deprecated, please use ZAMMAD_PROCESS_SESSIONS_JOBS_WORKERS instead. ZAMMAD_SESSION_JOBS_CONCURRENT: # Variables used by ngingx-proxy container for reverse proxy creations # for docs refer to https://github.com/nginx-proxy/nginx-proxy VIRTUAL_HOST: VIRTUAL_PORT: # Variables used by acme-companion for retrieval of LetsEncrypt certificate # for docs refer to https://github.com/nginx-proxy/acme-companion LETSENCRYPT_HOST: LETSENCRYPT_EMAIL: image: ${IMAGE_REPO:-ghcr.io/zammad/zammad}:${VERSION:-6.5.0-53} restart: ${RESTART:-always} volumes: - zammad-storage:/opt/zammad/storage depends_on: - zammad-memcached - zammad-postgresql - zammad-redis services: zammad-backup: <<: *zammad-service command: ["zammad-backup"] volumes: - ./zammad-backup:/var/tmp/zammad - zammad-storage:/opt/zammad/storage:ro user: 0:0 zammad-elasticsearch: image: bitnami/elasticsearch:${ELASTICSEARCH_VERSION:-8.18.0} restart: ${RESTART:-always} volumes: - elasticsearch-data:/bitnami/elasticsearch/data environment: # Enable authorization without HTTPS. For external access with # SSL termination, use solutions like nginx-proxy-manager. ELASTICSEARCH_ENABLE_SECURITY: 'true' ELASTICSEARCH_SKIP_TRANSPORT_TLS: 'true' ELASTICSEARCH_ENABLE_REST_TLS: 'false' # ELASTICSEARCH_USER is hardcoded to 'elastic' in the container. ELASTICSEARCH_PASSWORD: ${ELASTICSEARCH_PASS:-zammad} zammad-init: <<: *zammad-service command: ["zammad-init"] depends_on: - zammad-postgresql restart: on-failure user: 0:0 zammad-memcached: command: memcached -m 256M image: memcached:${MEMCACHE_VERSION:-1.6.38-alpine} restart: ${RESTART:-always} zammad-nginx: <<: *zammad-service command: ["zammad-nginx"] depends_on: - zammad-railsserver zammad-postgresql: environment: POSTGRES_DB: ${POSTGRES_DB:-zammad_production} POSTGRES_USER: ${POSTGRES_USER:-zammad} POSTGRES_PASSWORD: ${POSTGRES_PASS:-zammad} image: postgres:${POSTGRES_VERSION:-17.4-alpine} restart: ${RESTART:-always} volumes: - postgresql-data:/var/lib/postgresql/data zammad-railsserver: <<: *zammad-service command: ["zammad-railsserver"] zammad-redis: image: redis:${REDIS_VERSION:-7.4.3-alpine} restart: ${RESTART:-always} volumes: - redis-data:/data zammad-scheduler: <<: *zammad-service command: ["zammad-scheduler"] zammad-websocket: <<: *zammad-service command: ["zammad-websocket"] volumes: elasticsearch-data: driver: local postgresql-data: driver: local redis-data: driver: local zammad-storage: driver: local
Bei dieser Datei wurde das Zammad-Backup Volume entfernt, damit wir später einfacher auf unsere Backups zugreifen können. Ebenfalls wurden die Ports bei nginx entfernt.
4.2. Erstellen der docker-compose.override.yml
Um Änderungen an den Servicekonfigurationen vorzunehmen, ohne die ursprüngliche docker-compose.yml
-Datei zu ändern, erstellen wir eine docker-compose.override.yml
. Diese Methode behält die Integrität der Hauptkonfigurationsdatei bei und erleichtert die Wartung, sollten an der Originaldatei signifikante Änderungen vorgenommen werden.
Erstellen Sie die docker-compose.override.yml
Datei:
nano /opt/containers/zammad/docker-compose.override.yml
Fügen Sie den folgenden Inhalt ein:
services: zammad-backup: networks: default: zammad-elasticsearch: networks: default: zammad-init: networks: default: zammad-memcached: networks: default: zammad-nginx: environment: - VIRTUAL_HOST=zammad.euredomain.de - NGINX_SERVER_SCHEME=https labels: traefik.docker.network: proxy traefik.enable: "true" traefik.http.routers.zammad-secure.entrypoints: websecure traefik.http.routers.zammad-secure.middlewares: default@file traefik.http.routers.zammad-secure.rule: Host(`zammad.euredomain.de`) traefik.http.routers.zammad-secure.service: zammad-secure traefik.http.routers.zammad-secure.tls: "true" traefik.http.routers.zammad-secure.tls.certresolver: http_resolver traefik.http.services.zammad-secure.loadbalancer.server.port: "8080" networks: proxy: default: zammad-postgresql: networks: default: zammad-railsserver: networks: default: zammad-redis: networks: default: zammad-scheduler: networks: default: zammad-websocket: networks: default: networks: proxy: name: proxy external: true
In dieser Datei definieren wir spezifische Netzwerk- und Umgebungsparameter für unsere Zammad-Dienste und passen die Nginx Konfiguration an Traefik an. Wichtig ist hier, dass wir zammad.euredomain.de
durch die tatsächliche Domain ersetzen. Dies müsst ihr zwei Mal tun in der Datei. Das Netzwerk “proxy” ist extern und von Traefik verwaltet, während “default” das Standardnetzwerk für unsere Anwendung ist.
Diese docker-compose.override.yml
Datei ermöglicht uns eine flexible und wartungsfreundliche Konfiguration unserer Zammad-Installation. Durch die Verwendung dieser Datei können wir Änderungen an unserer Konfiguration vornehmen, ohne die ursprüngliche docker-compose.yml
-Datei zu beeinträchtigen, was insbesondere bei zukünftigen Updates und Upgrades nützlich ist.
5. Erstellung der .env-Datei
Jetzt benötigen wir einige spezifische Umgebungsvariablen für unsere Zammad-Installation. Diese Variablen sind in einer .env
Datei definiert, die wir aus dem Github-Repository von Zammad kopieren und in unsere eigene .env
Datei einfügen müssen.
Wir öffnen unsere neue Datei namens .env
im Verzeichnis /opt/containers/zammad/
nano /opt/containers/zammad/.env
und kopieren die folgenden Zeilen in die .env
Datei:
############################################# # Docker Environment Variables # https://docs.zammad.org/en/latest/install/docker-compose/environment.html ############################################# ############################################# # docker-compose.yml - These variables only need to be set if they do not have the default value. ############################################# # RESTART=always # Use a fixed Zammad version rather than the default. If you do so, # you are responsible to update this to newer patch level versions yourself. # VERSION=6.5.0-53 # You can also use floating versions that will give you automatic updates: # VERSION=6.2 # all patchlevel updates # VERSION=6 # including minor updates # VERSION=latest # all updates of stable versions, including major # VERSION=develop # bleeding-edge development version # IMAGE_REPO=ghcr.io/zammad/zammad # ELASTICSEARCH_VERSION=8.18.0 # MEMCACHE_SERVERS=zammad-memcached:11211 # MEMCACHE_VERSION=1.6.38-alpine # REDIS_URL=redis://zammad-redis:6379 # REDIS_VERSION=7.4.3-alpine # POSTGRES_VERSION=17.4-alpine # RAILS_TRUSTED_PROXIES= # ZAMMAD_HTTP_TYPE= # ZAMMAD_FQDN= # NGINX_PORT=8080 # NGINX_EXPOSE_PORT=8080 # NGINX_CLIENT_MAX_BODY_SIZE=50M # POSTGRES_DB=zammad_production # POSTGRES_PASS=zammad # POSTGRES_USER=zammad # POSTGRES_HOST=zammad-postgresql # POSTGRES_PORT=5432 # POSTGRESQL_OPTIONS=?pool=50 # ELASTICSEARCH_SCHEMA=http # ELASTICSEARCH_HOST=zammad-elasticsearch # ELASTICSEARCH_PORT=9200 # ELASTICSEARCH_USER=elastic # ELASTICSEARCH_PASS=zammad # ELASTICSEARCH_NAMESPACE=zammad # ELASTICSEARCH_REINDEX=true # Variables used by ngingx-proxy container for reverse proxy creations, # for docs refer to https://github.com/nginx-proxy/nginx-proxy. # VIRTUAL_HOST= # VIRTUAL_PORT= # Variables used by acme-companion for retrieval of LetsEncrypt certificate, # for docs refer to https://github.com/nginx-proxy/acme-companion. # LETSENCRYPT_HOST= # LETSENCRYPT_EMAIL= ############################################# # scenarios/add-cloudflare-tunnel.yml ############################################# # CLOUDFLARE_TUNNEL_TOKEN=mytoken ############################################# # scenarios/add-external-network-to-nginx.yml ############################################# # ZAMMAD_NGINX_EXTERNAL_NETWORK=mynetwork ############################################# # scenarios/add-external-network-to-elasticsearch.yml ############################################# # ZAMMAD_ELASTICSEARCH_EXTERNAL_NETWORK=mynetwork ############################################# # scenarios/add-hostport-to-elasticsearch.yml ############################################# # Defaults to 9200, set this if you need another value. # ELASTICSEARCH_EXPOSE_HTTP_PORT=9200
Bei Zammad auf der Homepage bekommt ihr eine Erklärung der Variablen: https://docs.zammad.org/en/latest/install/docker-compose/environment.html
Ich habe hier nichts angepasst. Wenn ihr eine bestimmte Version installieren wollt, so könnt ihr dies hier tun. Standardmäßig macht die Datei NICHTS, da alle Werte auskommentiert sind.
6. Zammad starten
Nachdem alle Vorbereitungen abgeschlossen sind, können wir nun Zammad starten. Wir wechseln zuerst in das Verzeichnis, in dem sich die Docker-Compose- und .env-Dateien befinden:
cd /opt/containers/zammad/
Jetzt können wir Zammad mit dem Befehl docker compose up -d
starten:
docker compose up -d
Dieser Befehl startet alle Dienste, die in der docker-compose.yml
– und docker-compose.override.yml
-Datei definiert sind, im Hintergrund und lässt sie laufen, selbst wenn wir die Terminal-Sitzung beenden.
Die Option -d
steht für “detached” und bedeutet, dass die Container im Hintergrund laufen und die Shell nach dem Start der Container zur Verfügung steht.
7. Elasticsearch optimieren
Um sicherzustellen, dass Elasticsearch effizient funktioniert, ist es möglicherweise notwendig, den vm.max_map_count
-Wert auf dem Host-System selbst anzupassen.
Der Wert für vm.max_map_count
bestimmt die maximale Anzahl von Speicherzuordnungen (Memory Mappings), die ein Prozess erstellen kann. Elasticsearch benötigt oft einen höheren Wert als den standardmäßigen, um optimal zu funktionieren.
Dafür öffnen wir die Datei /etc/sysctl.conf
auf dem Host-System:
nano /etc/sysctl.conf
Wur fügen die folgende Zeile am Ende der Datei hinzu und speichern die Änderungen:
vm.max_map_count=262144
Wenn du dir nicht sicher bist, welchen Wert du einstellen sollen, beginne mit dem doppelten des aktuellen Werts und erhöhe diesen schrittweise, bis Elasticsearch erfolgreich startet. Die offizielle Dokumentation empfiehlt einen Wert von mindestens 262144.
Wir starten unser System neu oder laden Sie die Konfiguration mit dem folgenden Befehl neu, um die Änderungen wirksam zu machen:
sysctl -p
Mit diesem Schritt stellen wir sicher, dass Elasticsearch die erforderlichen Ressourcen zur Verfügung hat, um optimal zu funktionieren.
8. FAQ
8.1. Warum wählen wir in diesem Setup Docker Volumes statt lokaler Mounts, und warum ist das Backup die Ausnahme zu dieser Regel?
Die Wahl, Docker Volumes anstelle von lokalen Mounts zu verwenden, hängt in erster Linie mit den besonderen Eigenschaften von Zammad und dessen Verwendung von verschiedenen Containern, insbesondere solchen, die auf Java basieren, zusammen. Java-basierte Anwendungen können bei unsachgemäßer Konfiguration zu Benutzerrechtsproblemen auf dem Server führen. Die Benutzung von Docker Volumes erleichtert das Management von Benutzerrechten, da Docker Volumes standardmäßig die korrekten Berechtigungen und Besitzer haben.
Zudem ist der Prozess der Anpassung der Docker-Compose-Datei, um lokale Mounts zu verwenden, tendenziell komplizierter und zeitaufwendiger. Er kann auch die Wartungsfreundlichkeit beeinträchtigen, da jede zusätzliche Anpassung der ursprünglichen Docker-Compose-Datei die Wahrscheinlichkeit erhöht, dass zukünftige Updates Probleme verursachen.
Aus all diesen Gründen habe ich mich dazu entschieden, in dieser Anleitung die Verwendung von Docker Volumes zu empfehlen und mich auf die bereitgestellte .env-Datei zu verlassen, anstatt eine eigene zu erstellen. Das Ziel ist es, einen klaren, einfach zu folgenden und wartungsfreundlichen Prozess zu gewährleisten.
Eine wichtige Ausnahme hierbei sind die Backups. Für diese nutzen wir lokale Mounts, um eine einfache Integration in bestehende Backup-Lösungen wie Borg Backup zu ermöglichen. Lokale Backups ermöglichen uns auch einen schnelleren Zugriff auf Backup-Dateien und eine flexiblere Handhabung der Backup-Datensicherung.
8.2. Wann werden Backups erstellt und wo kann ich diese finden?
In der docker-compose.yml ist standardmäßig folgendes hinterlegt:
BACKUP_DIR: "${BACKUP_DIR:-/var/tmp/zammad}" BACKUP_TIME: "${BACKUP_TIME:-03:00}" HOLD_DAYS: "${HOLD_DAYS:-10}"
Es werden also um 03:00 Uhr Backups erstellt und insgesamt 10 Tage gehalten. Der Speicherort befindet sich hier:
ls -l /opt/containers/zammad/zammad-backup/
Dort sollte auch bereits nach dem ersten Start ein Backup erstellt worden sein. Weitere Infos zum Backup findet ihr hier.
Hinweis
Hinweis zu Kommentaren
Wenn etwas nicht funktioniert, gehe noch einmal alle einzelnen Punkte in der Anleitung durch. Ich habe die Anleitung mehrfach getestet. Wenn du sicher bist, dass du alles richtig konfiguriert hast und das noch einmal überprüft hast, dann hinterlasse gerne einen Kommentar. Der Blog wird von Christian betrieben und er, sowie alle Co-Autoren, bemühen uns, alle Fragen und Probleme zu lösen – allerdings ist dies auch immer von unserer verfügbaren Zeit abhängig!
Bitte abonniert goNeuland, damit dieser Blog uns auch weiterhin viele Jahre Freude bereiten kann 😉
Hat alles wunderbar funktioniert. Nur mit dem Restore eines Backups komme ich nicht klar. Wie macht man das? Die verlinkte Seite zu Zammad war mir irgendwie auch keine Hilfe.
Könntet ihr die Anleitung mal auf den neusten Stand bringen? Scheinbar hat sich doch Einiges geändert. Ich bekomme Zammad damit nicht ans laufen.
Ist die Anleitung noch mit der neuen traefik v3 Doku kompatible?
Danke auch für diese Dokumentation in jedem Fall 🙂
Ich habe die .env ein wenig geändert, um die neusten Versionen zu nutzen:
Dadurch wird die stable version installiert und die Version ist nicht gelocked.
Hat noch jemand das Problem, dass ein Logo nicht gespeichert wird?
Es wird hochgeladen, angezeigt, dann speichere ich die Änderung ab und es wird als broken Link zur Datei angezeigt.
Dateien in den Ticket können gespeichert werden. Dort bleiben sie auch verfügbar.
Vielen Dank für diese Anleitung! Auch im generellen ein großes Lob für die tolle Arbeit!
Ich habe aber leider ein Problem, das ich nicht lösen kann.
Für Elasticsearch erhalte ich immer folgenden Fehler. Eine Idee, wie ich das lösen kann?
Vielen Dank im Voraus!