Homelab Infrastructure

The Homelab Infrastructure on the server (duckserver) follows an enterprise-grade DevOps pattern. It enforces strict security boundaries, isolated networking, centralized traffic routing, and non-root automated deployments.

Below is an exhaustive breakdown of every layer and topic comprising the homelab infrastructure as detailed across the sources:


  1. Server Directory Layout & Storage Hierarchy (/srv/)

The host system (duckserver, managed by user duckworth) organizes infrastructure components into a strict, modular directory structure under /srv/:

/srv/
├── backups/                    <-- Server & system backup storage [9]
├── docker/
│   ├── apps/                   <-- Isolated application deployment folders [9]
│   │   ├── astro_portfolio/    <-- Portfolio compose files & deployment context [9, 11]
│   │   └── mediaforce/         <-- Microservices application stack [9]
│   ├── caddy/                  <-- Central reverse proxy (Caddyfile, data, logs) [9, 10]
│   ├── cloudflared/            <-- Cloudflare Tunnel agent configuration [9]
│   └── monitoring/             <-- Server telemetry & monitoring [9]
├── github-runners/
│   ├── portfolio-runner/       <-- Dedicated CI/CD runner for portfolio [9, 12, 13]
│   └── mediaforce-runner/      <-- Dedicated CI/CD runner for mediaforce [9, 12]
└── storage/                    <-- Persistent storage volumes (media, DBs, downloads) [9]

  1. Inbound Traffic & Ingress Layer

Inbound web traffic enters the homelab through a secure, two-tiered gateway:

A. Cloudflare Tunnels (cloudflared)

  • Secure Ingress: Runs inside a dedicated container (cloudflare/cloudflared:latest).
  • Zero Open Inbound Ports: Establishes an outbound connection to Cloudflare’s edge. This exposes public websites to the internet without requiring open inbound ports on the home router or revealing the server's public IP address.

B. Central Caddy Reverse Proxy

  • Core Router: A centralized Caddy container (caddy:2.10) running in /srv/docker/caddy/ acts as the main HTTP/HTTPS router. It binds directly to host ports 80 and 443.
  • Caddyfile Routing Logic: Evaluates incoming host headers (e.g., http://ritikkumar.dev, http://mediaforce.ritikkumar.dev) and routes traffic to internal backends using the reverse_proxy directive:
  • Zero-Downtime Reloading: When updates are made to the Caddyfile, the central router reloads its configuration gracefully without dropping active HTTP connections via:

  1. Internal Network Topology & Container Security

Application security inside the homelab relies on Docker network isolation and zero host-port exposure:

Internet ──> Cloudflare Tunnel ──> Central Caddy (:80/:443)
                               (frontend bridge network)
                                          │ (Docker DNS)
                         `astro_portfolio` Container (:80)
  • Shared frontend Bridge Network: An external, shared Docker bridge network named frontend (external: true) links the central Caddy router to application containers.
  • Zero Host-Port Exposure: Application containers (such as astro_portfolio or mediaforce-web) do not publish ports to the host system (the ports: mapping block is deliberately omitted). Only infrastructure gateways (Caddy, Cloudflared) bind host network interfaces.
  • Docker Internal DNS Resolution: Central Caddy routes web requests over the frontend bridge using container name aliases (e.g., reverse_proxy astro_portfolio:80 or reverse_proxy mediaforce-api:3000) resolved natively by Docker's internal DNS engine.

  1. Application Container Orchestration (docker-compose.yml)

Each hosted application is managed as a standalone service inside its respective /srv/docker/apps/ folder:

services:
  portfolio:
    image: ghcr.io/ritikkumar27/astro_portfolio:latest
    container_name: astro_portfolio
    restart: unless-stopped
    networks:
      - frontend
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

networks:
  frontend:
    external: true
  • restart: unless-stopped: Guarantees that if the server reboots or the container process crashes, Docker automatically restores the service.
  • Log Rotation Guardrails: Limits container logs to a maximum of 10MB across 3 rotated files (max-size: "10m", max-file: "3") to prevent runaway logging from consuming server disk space.
  • Seamless Deployment: Running docker compose pull && docker compose up -d checks for updated image layers on GHCR, gracefully stops the old container, and starts a fresh container with zero downtime.

  1. Self-Hosted CI/CD Execution Layer (/srv/github-runners/)

Automated deployments are executed directly on the host hardware using dedicated GitHub Actions self-hosted runners:

A. Multi-Tenant Project Isolation

To enforce security boundaries, each project uses a separate runner directory (e.g., /srv/github-runners/portfolio-runner/ vs. /srv/github-runners/mediaforce-runner/). If one runner workspace is compromised, it cannot access unrelated applications.

B. Unprivileged Execution (github-runner / github-user)

  • Least Privilege Security: Runner daemons run under a dedicated, non-root system account (github-runner or github-user).
  • Docker Group Membership: The unprivileged runner user is added to the Linux docker group (usermod -aG docker github-runner), allowing it to execute docker compose pull and docker compose up -d without requiring elevated sudo rights.
  • Permission Setup: Folders are created and assigned explicitly to the runner account (sudo chown -R github-runner:github-runner /srv/github-runners/portfolio-runner), and configuration is executed via sudo -u github-runner ./config.sh.

C. systemd Service Integration (svc.sh)

  • The runner is registered as a background system daemon using GitHub's helper script (sudo ./svc.sh install github-runner and sudo ./svc.sh start).
  • Integrates directly with Linux systemd, ensuring runner daemons automatically start on server boot and remain active in an Idle / Green state to process deployment jobs.
  • Workflow jobs target runners via custom labels (e.g., runs-on: [self-hosted, homelab, portfolio]).

  1. GitHub Container Registry (GHCR) & Package Management

The homelab integrates with GHCR for container image distribution:

  • Server Authentication (--password-stdin): For private packages, the server authenticates against ghcr.io by piping a GitHub Personal Access Token (PAT) with read:packages scope into Docker:
  • Piping via --password-stdin prevents secret tokens from appearing in plain text inside terminal history logs.
  • Package Linking & 403 Forbidden Fixes:
    • Images pushed manually from a laptop land under the personal account's global "Packages" tab as unlinked packages. If a CI/CD pipeline later attempts to overwrite this package, it fails with a 403 Forbidden error.
    • Fix: In Package SettingsManage Actions access, developers must add the repository and grant it Write or Admin permissions. Alternatively, adding LABEL org.opencontainers.image.source="https://github.com/ritikkumar27/astro_portfolio" to the Dockerfile forces repository linkage automatically.