Docker Deployment

Running Zonal in Docker is the recommended approach for production church IT environments — it isolates the controller, makes upgrades a one-command operation, and works on any Linux server.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2
  • A Linux host (Ubuntu 22.04 LTS recommended)
  • Your SPL_LICENSE_KEY from your confirmation email

docker-compose.yml

services:
  controller:
    image: ghcr.io/steeplestack/zonal:latest
    restart: unless-stopped
    ports:
      - "4000:4000"   # Dashboard
      - "1883:1883"   # MQTT (monitors connect here)
    environment:
      SPL_LICENSE_KEY: ${SPL_LICENSE_KEY}
      SPL_DB_PATH: /data/zonal.db
    volumes:
      - zonal_data:/data

  # Optional: MQTT broker if you prefer an external one
  # mosquitto:
  #   image: eclipse-mosquitto:2
  #   ports: ["1883:1883"]

volumes:
  zonal_data:

Environment file

Create a .env file next to your docker-compose.yml:

SPL_LICENSE_KEY=SPL-XXXX-XXXX-XXXX-XXXX

Starting the controller

docker compose up -d
docker compose logs -f controller

The dashboard is available at http://<your-server-ip>:4000 within a few seconds of startup.

Upgrading

docker compose pull
docker compose up -d
docker image prune -f

Configuration and historical data are stored in the zonal_data volume and are not affected by upgrades.

Reverse proxy with HTTPS (recommended)

For production deployments, put the dashboard behind nginx with a Let's Encrypt certificate so you can access it securely from anywhere.

services:
  controller:
    image: ghcr.io/steeplestack/zonal:latest
    restart: unless-stopped
    expose: ["4000"]
    environment:
      SPL_LICENSE_KEY: ${SPL_LICENSE_KEY}
      VIRTUAL_HOST: zonal.yourchurch.org
      VIRTUAL_PORT: "4000"
      LETSENCRYPT_HOST: zonal.yourchurch.org
      LETSENCRYPT_EMAIL: [email protected]
    volumes:
      - zonal_data:/data

  nginx-proxy:
    image: nginxproxy/nginx-proxy:alpine
    ports: ["80:80", "443:443"]
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - certs:/etc/nginx/certs
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html

  acme-companion:
    image: nginxproxy/acme-companion
    volumes_from: [nginx-proxy]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - acme:/etc/acme.sh

volumes:
  zonal_data:
  certs:
  vhost:
  html:
  acme:

Firewall rules

PortProtocolPurposeExpose to
4000TCPDashboard / APILAN or reverse proxy only
1883TCPMQTT (monitors)LAN only
8883TCPMQTT over TLS (optional)LAN or internet

Health check

curl http://localhost:4000/api/health
# {"status":"ok","version":"2.3.1","licensed":true}