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:
- 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]
- 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 ports80and443. - Caddyfile Routing Logic: Evaluates incoming host headers (e.g.,
http://ritikkumar.dev,http://mediaforce.ritikkumar.dev) and routes traffic to internal backends using thereverse_proxydirective: - Zero-Downtime Reloading: When updates are made to the
Caddyfile, the central router reloads its configuration gracefully without dropping active HTTP connections via:
- 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_portfolioormediaforce-web) do not publish ports to the host system (theports: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
frontendbridge using container name aliases (e.g.,reverse_proxy astro_portfolio:80orreverse_proxy mediaforce-api:3000) resolved natively by Docker's internal DNS engine.
- 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 -dchecks for updated image layers on GHCR, gracefully stops the old container, and starts a fresh container with zero downtime.
- 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-runnerorgithub-user). - Docker Group Membership: The unprivileged runner user is added to the Linux
dockergroup (usermod -aG docker github-runner), allowing it to executedocker compose pullanddocker compose up -dwithout requiring elevatedsudorights. - 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 viasudo -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-runnerandsudo ./svc.sh start). - Integrates directly with Linux
systemd, ensuring runner daemons automatically start on server boot and remain active in anIdle / Greenstate to process deployment jobs. - Workflow jobs target runners via custom labels (e.g.,
runs-on: [self-hosted, homelab, portfolio]).
- 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.ioby piping a GitHub Personal Access Token (PAT) withread:packagesscope into Docker: - Piping via
--password-stdinprevents 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 Forbiddenerror. - Fix: In Package Settings → Manage Actions access, developers must add the repository and grant it
WriteorAdminpermissions. Alternatively, addingLABEL org.opencontainers.image.source="https://github.com/ritikkumar27/astro_portfolio"to theDockerfileforces repository linkage automatically.
- 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