Files

Zammad

Description

Zammad is an open-source helpdesk and ticketing system. Agents handle customer requests across email, chat, phone and web; customers can open tickets from a self-service portal.

Overview

This role deploys Zammad as an Infinito.Nexus web app using the upstream ghcr.io/zammad/zammad image (Rails app, WebSocket, scheduler, nginx, plus a one-shot init container that bypasses the setup wizard via auto_wizard.json). Search is provided by a bundled Elasticsearch container; PostgreSQL, Redis and Memcached are consumed from the central svc-db-* providers via sys-stk-full. Authentication uses direct OpenID Connect against the shared Keycloak client; LDAP federation and SMTP/IMAP via Stalwart are wired when their providers are present.

Cosmos

The diagram places Zammad in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).

flowchart LR
    subgraph deps [Dependencies]
        dep_svc_ai_litellm["svc-ai-litellm 🐳🐝"]
        dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
        dep_svc_db_memcached["svc-db-memcached 🐳🐝"]
        dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
        dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
        dep_svc_db_redis["svc-db-redis 🐳🐝"]
        dep_svc_net_tor["svc-net-tor 🐳🐝"]
        dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
        dep_web_app_hermes["web-app-hermes 🐳🐝"]
        dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
        dep_web_app_matomo["web-app-matomo 🐳🐝"]
        dep_web_app_openclaw["web-app-openclaw 🐳🐝"]
        dep_web_app_openwebui["web-app-openwebui 🐳🐝"]
        dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
        dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
        dep_web_svc_css["web-svc-css 💻"]
        dep_web_svc_logout["web-svc-logout 🐳🐝"]
    end
    subgraph role [web-app-zammad 🐳🐝]
        svc_litellm["litellm"]
        svc_sso["sso"]
        svc_ldap["ldap"]
        svc_email["email"]
        svc_logout["logout"]
        svc_dashboard["dashboard"]
        svc_matomo["matomo"]
        svc_prometheus["prometheus"]
        svc_css["css"]
        svc_postgres["postgres"]
        svc_redis["redis"]
        svc_memcached["memcached"]
        svc_zammad["zammad"]
        svc_railsserver["railsserver"]
        svc_websocket["websocket"]
        svc_search["search"]
        svc_elasticsearch["elasticsearch"]
        svc_precheck["precheck"]
        svc_init["init"]
        svc_scheduler["scheduler"]
        svc_nginx["nginx"]
        svc_tor["tor"]
        svc_container_backup["container_backup"]
        svc_zammadmcp["zammadmcp"]
        svc_openwebui["openwebui"]
        svc_hermes["hermes"]
        svc_openclaw["openclaw"]
        svc_flowise["flowise ❌"]
    end
    subgraph dependents [Dependents]
        dpt_web_app_nextcloud["web-app-nextcloud 🐳🐝"]
    end
    dep_svc_ai_litellm -. "0..1" .-> svc_litellm
    dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
    dep_svc_db_memcached -. "0..1" .-> svc_memcached
    dep_svc_db_openldap -. "0..1" .-> svc_ldap
    dep_svc_db_postgres -. "0..1" .-> svc_postgres
    dep_svc_db_redis -. "0..1" .-> svc_redis
    dep_svc_net_tor -. "0..1" .-> svc_tor
    dep_web_app_dashboard -. "0..1" .-> svc_dashboard
    dep_web_app_hermes -. "0..1" .-> svc_hermes
    dep_web_app_keycloak -. "0..1" .-> svc_sso
    dep_web_app_matomo -. "0..1" .-> svc_matomo
    dep_web_app_openclaw -. "0..1" .-> svc_openclaw
    dep_web_app_openwebui -. "0..1" .-> svc_openwebui
    dep_web_app_prometheus -. "0..1" .-> svc_prometheus
    dep_web_app_stalwart -. "0..1" .-> svc_email
    dep_web_svc_css -. "0..1" .-> svc_css
    dep_web_svc_logout -. "0..1" .-> svc_logout
    svc_litellm -. "0..1" .-> dpt_web_app_nextcloud

Solid 1:1 edges are fixed relationships; dashed 0..1 edges are conditional (enabled only in matching deployments); red 0..0 edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in meta/main.yml.

Features

  • Helpdesk ticketing: Multi-channel agent and customer surface for email, web and (optionally) chat tickets.
  • Direct OIDC SSO: Sign in through the shared Keycloak OIDC client without an oauth2-proxy sidecar; redirect URI is auto-registered.
  • LDAP federation: When svc-db-openldap is present, Zammad authenticates and provisions accounts against the central LDAP.
  • Mail-to-ticket: When web-app-stalwart is present, the helpdesk mailbox is auto-provisioned and Zammad polls it to create tickets from incoming mail.
  • Server-name alias: zammad.helpdesk.{{ DOMAIN_PRIMARY }} is a true vhost alias of helpdesk.{{ DOMAIN_PRIMARY }} (not a 301 redirect).
  • Bundled Elasticsearch: Search engine ships with the role until a central svc-db-elasticsearch exists. Its disk watermarks follow the disk size (85 / 90 / 95 % used, requiring at most 200 / 150 / 100 GB free, services.search.watermark); the CI and development variants lower those caps to 10 / 5 / 2 GB.
  • Search allocation precheck: search_allocation_precheck.sh creates a one-shard probe index named after the container and fails with the answer of _cluster/allocation/explain, which names the disk watermark when the data disk is too full, unless Elasticsearch allocates it. init runs it in front of zammad-init. In compose, the one-shot precheck service runs it first and init depends on that service, so compose up stops on a refusal. Swarm renders no precheck service, because depends_on does not gate init there. Instead the deploy waits until the newest init task is Complete: a refusing Elasticsearch leaves it Failed, the wait stops the deploy, and docker service logs of init carries the explanation. An init the stack deploy did not restart is already Complete and passes at once.
  • Search index per Elasticsearch major: The index lives in a directory named after the Elasticsearch major inside the zammad_search volume. A new major starts on an empty directory and zammad-init rebuilds the index from the database on the same deploy.
  • Wizard bypass: First deploy seeds auto_wizard.json so no manual setup UI step is required.

Quick Setup

Development

Clone, set up the workstation, and deploy Zammad onto the local stack:

git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-zammad full_cycle=false

Production

Run the published image to provision the inventory and deploy Zammad to a managed server (the mounted volume persists the inventory):

APP=web-app-zammad
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"

docker run --rm -it \
  -v "$PWD/inventories:/etc/infinito.nexus/inventories" \
  -e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
  ghcr.io/infinito-nexus/core/debian bash -c '
    INVENTORY=/etc/infinito.nexus/inventories/production
    infinito administration inventory provision "$INVENTORY" \
      --inventory-file "$INVENTORY/devices.yml" \
      --host "$HOST" \
      --include "$APP" \
      --vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
    infinito administration deploy dedicated "$INVENTORY/devices.yml" \
      --password-file "$INVENTORY/.password" \
      --diff -vv'

Developer Notes

Variant matrix lives in variants.yml. Service flags and image pins in services.yml. Credentials declared in secrets.yml.

Further Resources

Credits

Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).