Deployment Guide

Summary Overview

A production-grade workflow for deploying the homelab stack using Infrastructure as Code (IaC) and Docker Compose.

DevOpsDeploymentTraefik

Homelab Deployment

This guide details the operational procedure used to deploy and maintain the homelab services. It demonstrates a reproducible, code-driven approach rather than manual configuration, ensuring that the infrastructure can be rebuilt from scratch in minutes.

Prerequisites

  • Infrastructure: Azure VPS (Ubuntu 22.04+) with a static public IP.
  • Domain: Valid domain (e.g., ivanncabardo.tech) configured with A records pointing to the VPS IP.
  • Tools: Git, Docker Engine, Docker Compose v2.

Deployment Workflow

The deployment is broken down into layers to ensure stability and proper dependency management.

Layer 1: The Edge (Traefik)

The first step is establishing the secure perimeter. We deploy Traefik to handle certificate acquisition and routing. This layer must be healthy before any applications are exposed.

Service Configuration

We use docker-compose to define the Traefik service. Note the specific command flags used to enable the Docker provider and Let's Encrypt.

# traefik/docker-compose.yml

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik_proxy
    ports:
      - "80:80"    # HTTP (Redirects to HTTPS)
      - "443:443"  # HTTPS
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro # Access to listen for new containers
      - ./traefik.yml:/traefik.yml:ro # Static config
      - ./acme:/acme # Persistent storage for SSL certificates

Why this matters:

  • volumes: We mount acme to persist certificates across restarts, preventing us from hitting Let's Encrypt rate limits.
  • security_opt: no-new-privileges:true prevents the container from gaining additional privileges (privilege escalation).

Layer 2: Management (Portainer)

We deploy Portainer to provide a visual interface for Docker management. Crucially, we do not expose Portainer's default port 9000. Instead, we route it through Traefik.

Secure Routing Labels

# webapps/portainer/docker-compose.yml

services:
  portainer:
    # ... image and volume config ...
    networks:
      - traefik_proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.homelab.ivanncabardo.tech`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"

Breakdown:

  • traefik.enable=true: Explicitly tells Traefik to expose this container.
  • Host(...): Defines the domain name that routes to this service.
  • server.port=9000: Tells Traefik which internal port the container is listening on (since we aren't mapping it to the host).

Layer 3: Observability (Monitoring Stack)

The final layer brings visibility. We deploy the Prometheus/Grafana stack using a composed service definition.

Component Wiring

Prometheus needs to scrape metrics from other containers. This is achieved by joining them to the shared monitoring network.

# monitoring-stack/docker-compose.yaml

networks:
  monitoring:
    driver: bridge
  traefik_proxy:
    external: true

services:
  prometheus:
    # ... config ...
    networks:
      - monitoring
      - traefik_proxy # Needs to be here to be exposed via URL

  node-exporter:
    # ... config ...
    networks:
      - monitoring # Internal only, no Traefik labels needed

Architecture Note: node-exporter is NOT exposed to the internet. It sits on the internal monitoring network, where Prometheus scrapes it securely.

Maintenance & Updates

Maintenance is handled via GitOps principles to ensure the running state always matches the code:

  1. Update: git pull the latest configuration from the repository.
  2. Pull: docker-compose pull to fetch updated container images.
  3. Redeploy: docker-compose up -d to recreate containers with new configs/images. Docker only restarts changed containers.