Zabbix monitoring in Docker
Go to file
hygienic-books 7664d0f3f6 fix(zabbixserver): Zabbix server now works without override
Upstream github.com/zabbix/zabbix-docker/issues/1643 is now fixed.
This previously prevented us from using both HashiCorp Vault and the
default docker-entrypoint.sh file that came with the the
zabbix/zabbix-server-pgsql:alpine-7.2-latest image.

Upstreams commit hash 435e92f made it so that when Vault params are
present both the Zabbix server config params DBUser and DBPassword
are unset.
2025-03-14 09:54:37 +01:00
build-context/docker-data feat(zabbixserver): Update to Zabbix 7.2 2025-02-22 07:30:04 +01:00
env feat(zabbixserver): Update to Zabbix 7.2 2025-02-22 07:30:04 +01:00
.gitignore feat(zabbixserver): Update to Zabbix 7.2 2025-02-22 07:30:04 +01:00
common-settings.yaml feat(zabbixserver): Update to Zabbix 7.2 2025-02-22 07:30:04 +01:00
compose.override.yaml fix(zabbixserver): Zabbix server now works without override 2025-03-14 09:51:04 +01:00
LICENSE Initial commit 2024-05-13 21:16:16 +02:00
README.md fix(zabbixserver): Zabbix server now works without override 2025-03-14 09:54:37 +01:00

Zabbix Docker Compose files

Docker Compose files to spin up an instance of Zabbix.

How to run

Add a COMPOSE_ENV_FILE and save its location as a shell variable along with the location where this repo lives, here for example /opt/containers/zabbixserver plus all other variables. At env/fqdn_context.env.example you'll find an example environment file.

When everything's ready start Zabbix with Docker Compose, otherwise head down to Initial setup first.

Environment

Make sure that Zabbix' upstream repo at github.com/zabbix/zabbix-docker is checked out locally. We're going with example dir /opt/git/github.com/zabbix/zabbix-docker/branches/latest. We're also assuming that this repo exists at /opt/containers/zabbixserver.

export UPSTREAM_REPO_DIR='/opt/git/github.com/zabbix/zabbix-docker/branches/latest'
export UPSTREAM_COMPOSE_FILE="${UPSTREAM_REPO_DIR%/}"'/docker-compose_v3_alpine_pgsql_latest.yaml'
export UPSTREAM_ENV_FILE="${UPSTREAM_REPO_DIR%/}"'/.env'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT_NAME='zabbixserver-'"${COMPOSE_CTX}"
export COMPOSE_ENV_FILE=<add accordingly>
export COMPOSE_OVERRIDE='/opt/containers/zabbixserver/compose.override.yaml'

In Zabbix' Git repo check out latest tag for whatever version you want to use, we're going with the latest 7.2.* version.

git -C "${UPSTREAM_REPO_DIR}" reset --hard origin/trunk
git -C "${UPSTREAM_REPO_DIR}" checkout trunk
git -C "${UPSTREAM_REPO_DIR}" pull
git -C "${UPSTREAM_REPO_DIR}" checkout "$(git --no-pager -C "${UPSTREAM_REPO_DIR}" tag -l --sort -version:refname | grep -Fi -- '7.2.' | head -n 1)"

Context

On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:

docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'

Pull

Pull images from Docker Hub verbatim.

docker compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" pull

Copy to target

Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local docker save and a remote docker load. Consider the helper mini-project quico.space/Quico/copy-docker where copy-docker.sh allows the following workflow.

images="$(docker compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" config | grep -Pi -- 'image:' | awk '{print $2}' | sort | uniq)"
while IFS= read -u 10 -r image; do
    copy-docker "${image}" fully.qualified.domain.name
done 10<<<"${images}"

This will for example copy over:

REPOSITORY                      TAG
postgres                        16-alpine
zabbix/zabbix-web-nginx-pgsql   alpine-7.2-latest
zabbix/zabbix-server-pgsql      alpine-7.2-latest
busybox                         latest

Start

docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" up --detach

Clean up

docker --context 'fully.qualified.domain.name' system prune -af
docker system prune -af

Initial setup

We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.

Datasets

Create ZFS datasets and set permissions as needed.

  • Parent dateset

    export "$(grep -Pi -- '^CONTEXT=' "${COMPOSE_ENV_FILE}")"
    zfs create -o canmount=off zpool/data/opt
    zfs create -o mountpoint=/opt/docker-data zpool/data/opt/docker-data
    
  • Container-specific datasets

    zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/config'
    zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/data'
    zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/config'
    zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/data'
    zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixwebnginx/config'
    
  • Change ownership

    chown -R 70:70 '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/'*
    chown -R 101:101 '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixwebnginx/config/'*
    

    The PostgreSQL container will run its processes as user ID 70, the Zabbix web frontend container will be using user ID 101.

Additional files

Per Datasets your Docker files will live at '/opt/docker-data/zabbixserver-'"${CONTEXT}". Over in build-context you'll find a subdirectory docker-data that has an example file and directory structure that explains the layout you'll want to create at '/opt/docker-data/zabbixserver-'"${CONTEXT}". Match the postgres to your postgres dir, the zabbixserver dir to your zabbixserver dir and lastly the zabbixwebnginx dir to yours.

docker-data/
├── postgres
│   ├── cert
│   │   ├── .ZBX_DB_CA_FILE
│   │   ├── .ZBX_DB_CERT_FILE
│   │   └── .ZBX_DB_KEY_FILE
│   └── docker-entrypoint-initdb.d
│       └── init-user-db.sh
├── zabbixserver
│   ├── config
│   │   └── cert
│   │       ├── .ZBX_SERVER_CA_FILE
│   │       ├── .ZBX_SERVER_CERT_FILE
│   │       └── .ZBX_SERVER_KEY_FILE
│   └── data
│       ├── usr
│       │   └── lib
│       │       └── zabbix
│       │           ├── alertscripts
│       │           └── externalscripts
│       └── var
│           └── lib
│               └── zabbix
│                   ├── dbscripts
│                   ├── enc
│                   ├── export
│                   ├── mibs
│                   ├── modules
│                   ├── snmptraps
│                   ├── ssh_keys
│                   └── ssl
│                       ├── certs
│                       ├── keys
│                       └── ssl_ca
└── zabbixwebnginx
    └── config
        ├── cert
        │   ├── dhparam.pem
        │   ├── ssl.crt
        │   └── ssl.key
        └── modules

postgres (PostgreSQL)

In postgres/cert place SSL certificate files that Postgres should serve to TLS-capable database clients for encrypted database connections such as for a domain db.zabbix.example.com. .ZBX_DB_CA_FILE is a certificate authority (CA) certificate, .ZBX_DB_CERT_FILE is a "full chain" certificate as in your domain's certificate followed by any intermediate certs concatenated one after the other. Lastly .ZBX_DB_KEY_FILE is your cert's unencrypted key file.

In postgres/config/docker-entrypoint-initdb.d/init-user-db.sh you'll find an example script file that - when your Postgres database is uninitialized - will create a second Postgres account in your database. Check out the example environment variables file env/fqdn_context.env.example and specifically ZBX_DB_USERNAME_PW and ZBX_DB_USERNAME_RO to define a password and a username.

Zabbix' PostgreSQL instance by default doesn't expose a TCP port outside of its container. This setup, however, assumes that you have for example a Grafana instance or a similar entity that wants to directly connect to Postgres. Dedicated read-only database credentials come in handy in that situation.

zabbixserver (main Zabbix server daemon)

In zabbixserver/config/cert place your SSL cert files. These are what the Zabbix server process serves to clients that connect to it such as server.zabbix.example.com. As with PostgreSQL you'll need a CA cert, a domain cert and a key file; file names are .ZBX_SERVER_CA_FILE, .ZBX_SERVER_CERT_FILE and .ZBX_SERVER_KEY_FILE.

There's also zabbixserver/data with what looks like a daunting amount of subdirectories. In our example they are all empty and they all belong to bind mounts that are configured with create_host_path: true.

    - type: bind
      source: /opt/docker-data/zabbixserver-${CONTEXT}/zabbixserver/data/usr/lib/zabbix/alertscripts
      target: /usr/lib/zabbix/alertscripts
      read_only: true
      bind:
-->     create_host_path: true

If you don't want to mount any files into your Zabbix instance you can leave zabbixserver/data alone and Docker will create the necessary subdirs on your Docker host on container start.

If you do want all subdirs feel free to go like this:

cd '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/data'
mkdir -p {'./usr/lib/zabbix/'{'alert','external'}'scripts','./var/lib/zabbix/'{'dbscripts','enc','export','mibs','modules','snmptraps','ssh_keys','ssl/'{'certs','keys','ssl_ca'}}}

This will create the entire directory tree underneath zabbixserver/data:

data/
├── usr
│   └── lib
│       └── zabbix
│           ├── alertscripts
│           └── externalscripts
└── var
    └── lib
        └── zabbix
            ├── dbscripts
            ├── enc
            ├── export
            ├── mibs
            ├── modules
            ├── snmptraps
            ├── ssh_keys
            └── ssl
                ├── certs
                ├── keys
                └── ssl_ca

zabbixwebnginx (Nginx web server)

First things first, directory zabbixwebnginx/config/modules is empty and due to create_host_path: true will be created anyway if you don't create it yourself so no worries there. In zabbixwebnginx/config/cert - as the name suggests - you'll place frontend SSL cert files. That's the domain certificate you want to get served when visiting Zabbix frontend with a web browser. In line with our earlier examples this might be a cert for example for zabbix.example.com.

Note that the file names here look relatively normal as opposed to .ZBX_SERVER_CERT_FILE and .ZBX_DB_CERT_FILE from before. We will be bind-mounting the entire cert directory like so:

- type: bind
  source: /opt/docker-data/zabbixserver-${CONTEXT}/zabbixwebnginx/config/cert
  target: /etc/ssl/nginx
  read_only: true
  bind:
    create_host_path: true

The cert dir ends up getting bind-mounted into /etc/ssl/nginx inside the container. Since Zabbix uses a standard Nginx setup we stick to the Nginx way of calling a default cert and key file. Store your full certificate chain as ssl.crt and the corresponding unencrypted key as ssl.key. Make sure to also save a dhparam.pem parameters file. You can get one such file the quick and dirty way for example from Mozilla at https://ssl-config.mozilla.org/ffdhe2048.txt - just save it as dhparam.pem if you're so inclined. You can alternatively render a file yourself. Assuming the parallel binary exists on your machine you can follow unix.stackexchange.com/a/749156 like so:

seq 10000 | parallel -N0 --halt now,success=1 openssl dhparam -out dhparam.pem 4096

This starts as many parallel openssl dhparam processes as you have CPU cores (assuming you have at most 10,000 cores). Processes essentially race each other which typically lowers waiting time for a finished parameters file by an order of magnitude since you only need one random process to finish. On a moderately modern desktop CPU with four cores this will take about 30 seconds.

When done head back up to How to run.

Development

Conventional commits

This project uses Conventional Commits for its commit messages.

Commit types

Commit types besides fix and feat are:

  • refactor: Keeping functionality while streamlining or otherwise improving function flow
  • docs: Documentation for project or components

Commit scopes

The following scopes are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:

  • zabbixserver: A change to how the zabbixserver service component works
  • build: Build-related changes such as Dockerfile fixes and features.
  • mount: Volume or bind mount-related changes.
  • net: Networking, IP addressing, routing changes
  • meta: Affects the project's repo layout, file names etc.