Building a homelab (3/5): Containerized Services: DNS, Monitoring, and Self-Hosted Apps

Empower your digital life: In a world of clouds, I choose to build my own — where privacy reigns and my data is truly mine.

In Part 1, I introduced my homelab setup. In Part 2, I covered the GitOps pipeline with Renovate and Komodo. Now let’s look at the actual applications that make this environment useful.

From local DNS blocking to RSS reading and media streaming, everything is self-hosted. And since I’m running on a mix of Pi boards and a Proxmox VM, distribution of services matters just as much as the apps themselves.

Service Distribution Overview

Proxmox VM srv-prod-01

Service Description
ArchiveBox Bookmark Archival
Audiobookshelf Audiobook Management
Calibre EBook Management
Dozzle Logs Monitoring
Homeassistant Homeautomation, HomeKit Bridge
Homepage Home Dashboard
Immich Photo Library
Jellyfin Media Server
Komodo Docker Management
Komodo Periphery Docker Management Agent
Komodo ntfy Alerter Bridge
Libation Audibook Downloader
Mealie Recipe Management
MeTube Video & Audio Downloader
Miniflux RSS
ntfy Push Service
Paperless Document Management
Penpot Design Tool
SABnzbd Usenet Downloader
Traefik Reverse Proxy, SSL, etc.
Tubesync Video & Audio Downloader
Uptime Kuma Monitoring
Vaultwarden Password Management

RPI DNS01

Service Description
Adguard Home DNS Server
Adguard Home Sync Adguard Home Config Sync
Dozzle Agent Logs Agent
Komodo Periphery Docker Management Agent

RPI DNS02

Service Description
Adguard Home DNS Server
Dozzle Agent Logs Agent
Komodo Periphery Docker Management Agent

This setup balances critical services (DNS) across two Pis for resilience, while keeping heavier services (media, apps) centralized on the Ubuntu VM.

Local DNS with Adguard Home

AdGuard Home is my go-to solution for local DNS with ad and tracker blocking. I run two independent instances — one on each Pi — to ensure redundancy.

Why two instances?

  • Ensures DNS still works if one Pi reboots or fails

  • Provides distributed DNS for clients across VLANs

  • Enables load distribution and fault tolerance

To keep their configs in sync, I run adguardhome-sync on dns01:

services:
  adguardhome-sync:
    image: lscr.io/linuxserver/adguardhome-sync:v0.7.6-ls138@sha256:21b0311d8e0aecca093f6aa0c15a91e293de108d86477da768816eb75af130bb
    container_name: adguardhome-sync
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ:-UTC}
    volumes:
      - ${VOLUME_PATH}/config:/config
    ports:
      - 8090:8080
    restart: unless-stopped

The mounted config file then adds dns02 as a sync target. This way every change I make on dns01 will automatically be reflected on dns02 as well.

An image showing the adguard web UI.

📈 Monitoring with Uptime Kuma

Uptime Kuma runs in a Docker container on the Ubuntu VM. It monitors:

  • My public WireGuard endpoint

  • All internal apps via their Traefik routes / docker containers

  • DNS uptime on both Pi nodes

  • External services like GitHub and my VPS

  • Cron Jobs and Backup Tasks

It’s lightweight, supports alerting, and even shows response time history.

An image showing the uptime-kuma web UI.

📚 Logging with Dozzle

Each of my Raspberry Pis runs a Dozzle agent, which streams container logs to the central Dozzle instance. Log files written to disk are available through a seperate output stream I create. This gives me:

  • One place to view logs from every device

  • Live log tailing via browser

  • Easy debugging if something breaks

On the central VM, I run the Dozzle web UI and configure it to connect to the remote agents or output streams:

dozzle:
    image: amir20/dozzle:v8.12.20@sha256:6e3c64615e15493dbd2a476650d17b1b0038ba7dbd3cfe1a611df64ed57e602a
    container_name: dozzle
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
    networks:
      - internal
      - proxy
    environment:
      - DOZZLE_LEVEL=${DOZZLE_LEVEL}
      - DOZZLE_ENABLE_ACTIONS=${DOZZLE_ENABLE_ACTIONS}
      - DOZZLE_ENABLE_SHELL=${DOZZLE_ENABLE_SHELL}
      - DOZZLE_NO_ANALYTICS=${DOZZLE_NO_ANALYTICS}
      - DOZZLE_FILTER=${DOZZLE_FILTER}
      - DOZZLE_AUTH_PROVIDER=${DOZZLE_AUTH_PROVIDER}
      - DOZZLE_HOSTNAME=${DOZZLE_HOSTNAME}
      - DOZZLE_REMOTE_AGENT=${DOZZLE_REMOTE_AGENT}
    volumes:
      - ${VOLUME_PATH}/data:/data
      - ${VOLUME_PATH}/certs:/certs
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dozzle.rule=Host(`mydomain.de`)"
      - "traefik.http.routers.dozzle.entrypoints=https"
      - "traefik.http.routers.dozzle.tls=true"
      - "traefik.http.services.dozzle.loadbalancer.server.port=8080"
      - "traefik.docker.network=proxy"

  # example of an output stream
  dozzle-traefik:
    container_name: dozzle-traefik
    image: alpine@sha256:6662067ba6f090f8a2c8b5b50309692be03bffef2729b453425edd4f26343377
    volumes:
      - ${TRAEFIK_LOG}:/var/log/stream.log
    command:
      - tail
      - -f
      - /var/log/stream.log
    network_mode: none
    restart: unless-stopped
    labels:
      - dev.dozzle.group=traefik
An image showing the dozzle web UI.

🛜 Traefik: The Ingress Brain

Every internal service is routed through Traefik, my reverse proxy of choice. Key features in my setup:

  • Automatic TLS via Let's Encrypt

  • Internal-only DNS resolution (e.g., mydomain.de)

  • Container labels define routing rules declaratively

In Adguard Home i added Custom DNS Rewrite rule. Whenever my DNS server gets a request to resolve the IP address for *.mydomain.de it points to my traefik instance which then routes based on the host name to the actual docker containers or machines. Since traefik comes certbot/acme out of the box I can use my own domain to SSL encrypt this connection with a certificate requested from Let's Encrypt. To set things up I mostly followed this tutorial here so I won't go into detail: Traefik Tutorial.

➡️ Conclusion

Once you feel the excitement and joy of deploying actually useful apps to your homelab there is no step back. Watching a movie on Jellyfin, reading my RSS feeds through Miniflux, storing passwords in my own, private vaultwarden instance just feels very rewarding. On top I can access these services via my own custom domain SSL encrypted, although I do not expose them to the internet. In the next part we have a closer look at my network setup and how I stepped up my game here.

Posted in homelab