Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsTraining
StudyProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Training
Study
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

2868+ Articles
168+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. Projects
  3. Zabbix: Self-Hosted Infrastructure Monitoring with Docker Compose
Zabbix: Self-Hosted Infrastructure Monitoring with Docker Compose
PROJECTIntermediate

Zabbix: Self-Hosted Infrastructure Monitoring with Docker Compose

Deploy Zabbix 7.0 LTS with PostgreSQL in Docker, enroll agents, tune triggers, and route alerts to Discord without the alert fatigue.

Dylan H.

Projects

September 16, 2026
7 min read
3-5 hours

Tools & Technologies

Zabbix Server 7.0 LTSZabbix Web (nginx)Zabbix Agent2Docker ComposePostgreSQL

Overview

Prometheus and Grafana get most of the homelab monitoring attention, but they're built around pull-based metrics scraping and PromQL dashboards. Zabbix takes a different, older, and in some ways more complete approach: it's a single all-in-one platform that handles agent-based polling, SNMP, trap-based push metrics, trigger evaluation, escalation chains, and templated auto-discovery out of the box — no separate exporter ecosystem to assemble. For infrastructure that isn't already emitting Prometheus-format metrics (network switches, UPS units, Windows servers, legacy appliances), Zabbix's agent + SNMP model is usually less work to stand up.

This build deploys Zabbix 7.0 LTS (the current long-term-support release, patched through 7.0.19) with PostgreSQL as the backend, using the official Docker images rather than a from-source install. It covers enrolling a Linux host and an SNMP-only device, tuning triggers so a single flapping service doesn't page you five times, and wiring alert actions to a Discord webhook instead of email.

Architecture

┌──────────────┐   agent poll :10050   ┌─────────────────────┐
│ Linux host    │──────────────────────▶│                      │
│ (zabbix-agent2)│                       │   Zabbix Server      │
└──────────────┘                       │   :10051 (trapper)   │
┌──────────────┐   SNMP UDP :161       │                      │──▶ PostgreSQL 16
│ Switch/UPS    │──────────────────────▶│  history · triggers  │    (history + config)
└──────────────┘                       └──────────┬───────────┘
                                                    │ API/RPC
                                          ┌─────────▼──────────┐
                                          │  Zabbix Web (nginx) │──▶ Discord webhook
                                          │  :8080 dashboards   │    (alert actions)
                                          └─────────────────────┘

Zabbix Server does the polling/trapping and trigger evaluation; Zabbix Web is a stateless frontend that reads/writes the same PostgreSQL database and has no monitoring logic of its own. Agents are pull-polled by default, but can also push data proactively ("active checks") to reduce server-side connection overhead on larger fleets.

Step-by-Step Build

1. Lay out the Docker Compose stack

# docker-compose.yml
services:
  postgres-server:
    image: postgres:16-alpine
    container_name: zabbix-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: ${ZABBIX_DB_PASSWORD}
      POSTGRES_DB: zabbix
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U zabbix"]
      interval: 10s
      retries: 5
 
  zabbix-server:
    image: zabbix/zabbix-server-pgsql:alpine-7.0-latest
    container_name: zabbix-server
    restart: unless-stopped
    depends_on:
      postgres-server:
        condition: service_healthy
    environment:
      DB_SERVER_HOST: postgres-server
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: ${ZABBIX_DB_PASSWORD}
      POSTGRES_DB: zabbix
    volumes:
      - ./data/server-alertscripts:/usr/lib/zabbix/alertscripts
    ports:
      - "10051:10051"
 
  zabbix-web:
    image: zabbix/zabbix-web-nginx-pgsql:alpine-7.0-latest
    container_name: zabbix-web
    restart: unless-stopped
    depends_on:
      - zabbix-server
    environment:
      DB_SERVER_HOST: postgres-server
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: ${ZABBIX_DB_PASSWORD}
      POSTGRES_DB: zabbix
      ZBX_SERVER_HOST: zabbix-server
      PHP_TZ: America/Vancouver
    ports:
      - "8080:8080"
 
  zabbix-agent2:
    image: zabbix/zabbix-agent2:alpine-7.0-latest
    container_name: zabbix-agent2
    restart: unless-stopped
    environment:
      ZBX_HOSTNAME: docker-host-01
      ZBX_SERVER_HOST: zabbix-server
    privileged: true
    network_mode: host

The official images auto-run schema migration on first boot — no manual zcat create.sql.gz | psql step like a from-source install requires. Requires Docker Compose 2.24.0+; run docker compose up -d and give the server container a minute to finish the initial DB import before hitting the web UI.

2. First login and change the default password

# wait for zabbix-web healthy, then browse to :8080
# default credentials: Admin / zabbix

Change the Admin password immediately under Users → Admin → Change password — the default is well-known and Zabbix's own hardening guide calls it out as the first thing to fix on a new install.

3. Enroll a Linux host via Agent2

On the target host (or as a sidecar container per service above), point the agent at the server:

# /etc/zabbix/zabbix_agent2.conf
Server=zabbix-server.example.lan
ServerActive=zabbix-server.example.lan
Hostname=docker-host-01

In the Zabbix frontend: Data collection → Hosts → Create host, set the hostname to match Hostname above, attach it to a host group (e.g. "Docker hosts"), and link the Linux by Zabbix agent template — this template ships pre-built items (CPU, memory, disk, network) and triggers, so there's no manual item authoring for baseline OS metrics.

4. Add an SNMP-only device (switch, UPS, etc.)

Devices that can't run an agent (managed switches, PDUs, printers) go through SNMP instead:

  1. Create host → set SNMP interface, port 161, community string (SNMPv2c) or SNMPv3 credentials.
  2. Link a matching template — Zabbix ships official templates for common vendors under Network devices (e.g. "Cisco IOS SNMP").
  3. Confirm data is arriving: Monitoring → Latest data, filter by host, check for non-stale timestamps.

5. Tune triggers to avoid alert fatigue

The default templates alert on raw thresholds, which flaps on transient spikes. Wrap trigger expressions in a time-window aggregate instead of a point-in-time check:

# Bad: fires on any single high reading
last(/docker-host-01/system.cpu.load[percpu,avg1])>5

# Better: sustained load over 5 minutes
min(/docker-host-01/system.cpu.load[percpu,avg1],5m)>5

Set trigger severity deliberately (Warning vs. High vs. Disaster) — actions can filter on severity, which is how you keep "disk 90% full" out of the same notification channel as "host unreachable."

6. Route alerts to Discord instead of email

Zabbix has no native Discord media type, but a webhook-based one is straightforward via a JavaScript alert script:

  1. Alerts → Media types → Create media type → type Webhook.
  2. Add a URL parameter pointing at the Discord webhook URL, and a script that POSTs {"content": "{ALERT.SUBJECT}\n{ALERT.MESSAGE}"} as JSON.
  3. Users → Admin → Media → add the new media type.
  4. Alerts → Actions → Trigger actions → create a condition (e.g. severity ≥ Warning) and an operation that sends via the Discord media type.

Test with a manufactured trigger (temporarily lower a threshold) before trusting it for a real incident.

Testing

  • Agent connectivity: docker compose exec zabbix-server zabbix_server -R config_cache_reload, then Monitoring → Latest data — confirm data points are landing with recent timestamps, not just "Zabbix agent is available" ping items.
  • SNMP reachability: snmpwalk -v2c -c <community> <device-ip> from the server host — if this fails outside Zabbix, the item will fail inside it too.
  • Trigger logic: force a threshold breach (e.g. stress-ng --cpu 4 --timeout 300s on a test host) and confirm the trigger fires in Monitoring → Problems within the expected evaluation window, then resolves cleanly after load drops.
  • Alert delivery: fire a manual test event and confirm the Discord message lands in the right channel with the right severity color — silent failures here usually mean the webhook script's exit code isn't 0.
  • Database growth: docker compose exec postgres-server psql -U zabbix -c "SELECT pg_size_pretty(pg_database_size('zabbix'));" — history/trends tables grow fast with short polling intervals; check this weekly for the first month.

Deployment Notes

  • Set housekeeping (Administration → General → Housekeeping) to trim history/trends retention — the defaults keep raw history for 31 days, which is generous for a homelab-scale install and adds up in PostgreSQL disk usage.
  • Back up the PostgreSQL volume, not just the Zabbix config export — host/item/trigger definitions live in the same database as historical data.
  • For fleets beyond ~500 monitored items, consider TimescaleDB as the PostgreSQL storage backend (postgres-server image swap to timescale/timescaledb) — Zabbix has first-class support for it and it meaningfully reduces history-table bloat.
  • network_mode: host on the agent container is the simplest way to get accurate host-level network/interface metrics; it does trade away Docker's network isolation for that container specifically.

Extensions

  • Combine with the site's Prometheus + Grafana build — Zabbix can scrape a Prometheus endpoint as an HTTP agent item, letting one alerting/escalation engine cover both agent-based and Prometheus-native services.
  • Layer on the Traefik reverse proxy build to put the web frontend behind real TLS instead of a bare :8080.
  • Use Zabbix's built-in network discovery rules to auto-register new hosts on a subnet instead of manually creating each one.
  • Script report_generate-style capacity reports (host-group scoped PDF exports) for a monthly infrastructure capacity review without touching Grafana at all.

Sources: Zabbix Installation from Containers, zabbix/zabbix-docker GitHub, zabbix-server-pgsql on Docker Hub, zabbix-web-nginx-pgsql on Docker Hub

#zabbix#monitoring#docker#postgresql#snmp#alerting#homelab

Related Articles

Keycloak SSO: Self-Hosted Identity Provider for Your Homelab

Deploy Keycloak with Docker Compose and PostgreSQL to build a centralised single sign-on platform for your homelab services, with OIDC integration for...

11 min read

Runtime Security Monitoring with Falco: Detect Container

Deploy Falco on a Docker host to monitor container syscalls at the kernel level, write custom homelab detection rules, and route real-time alerts through.

12 min read

Building a Wazuh XDR + SIEM Homelab

Deploy a full Wazuh stack in Docker to gain host-based intrusion detection, file integrity monitoring, vulnerability scanning, and active response across your…

14 min read
Back to all Projects