Skip to content

lcrostarosa/homelab-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

13 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Home Lab Server Stack

A complete Infrastructure-as-Code (IaC) setup for running Home Assistant and supporting services on a Raspberry Pi (or any Linux server), with secure external access via Cloudflare Tunnel. I use something similar to this, and since AI can template out my configuration, I might as well help others get a homelab up and running.

Few disclaimers

  • MIT license, use at your own risk
  • Cut an issue if you have trouble and will try to help
  • Feel free to contribute any cool things you are adding
  • Nextcloud, grafana, is still a work in progress on my end

πŸ“‘ Table of Contents


🎯 What This Provides

  • Hardened Linux Server - Disables root access, enables backups, sets up firewall with UFW, installs docker and some other things that I like to use on in linux.
  • Home Assistant - Open-source home automation
  • Traefik - Modern reverse proxy with automatic SSL
  • Cloudflare Tunnel - Secure external access without port forwarding
  • AdGuard Home - Network-wide ad blocking and DNS
  • Prometheus + Grafana - Monitoring stack with beautiful dashboards
  • Nextcloud - Self-hosted cloud storage (Google Drive alternative)
  • Homarr - Beautiful dashboard for your services
  • Portainer - Docker container management
  • OpenVPN - VPN access to your home network
  • AirConnect - Chromecast to AirPlay bridge

🐳 Services Overview

Service Port Purpose External Access
Home Assistant 8123 Smart home automation platform. Controls lights, sensors, switches, and automations. The brain of your smart home. βœ… Via Cloudflare Tunnel
Traefik 80, 8080 Reverse proxy and load balancer. Routes incoming traffic to the correct service and adds security headers. Dashboard on 8080 (localhost only). Internal routing only
Cloudflared - Cloudflare Tunnel connector. Creates a secure outbound connection to Cloudflare, enabling HTTPS access without opening ports on your router. Outbound only
AdGuard Home 53, 3000 Network-wide ad blocker and DNS server. Blocks ads, trackers, and malware at the DNS level for all devices on your network. βœ… Web UI via Tunnel
Homarr 7575 Beautiful dashboard for your homelab. Provides a single page to access all your services with customizable widgets. ⚠️ Localhost only
Portainer 9000 Docker management UI. View containers, logs, networks, and volumes through a web interface instead of CLI. ⚠️ Localhost only
AirConnect - Chromecast/DLNA to AirPlay bridge. Allows Apple devices to stream audio to Chromecast or DLNA speakers via AirPlay. ❌ Local only

Monitoring Stack (Prometheus + Grafana)

Service Port Purpose External Access
Prometheus 9090 Time-series database for metrics. Collects and stores metrics from all your services. Query with PromQL. ⚠️ Localhost only (no auth!)
Grafana 3001 Metrics visualization and dashboards. Beautiful graphs and alerts for your homelab. Pre-configured with Prometheus. βœ… Via Tunnel (has auth)
Node Exporter 9100 System metrics collector. Exposes CPU, memory, disk, and network stats from the host machine to Prometheus. ❌ Internal only
cAdvisor 8081 Container metrics collector. Monitors resource usage and performance of all Docker containers. ❌ Internal only

Cloud Storage (Nextcloud)

Service Port Purpose External Access
Nextcloud 8082 Self-hosted cloud storage and collaboration platform. Sync files, calendars, contacts across devices. Alternative to Google Drive/Dropbox. βœ… Via Tunnel
MariaDB - Database backend for Nextcloud. Stores user data, file metadata, and app configurations. ❌ Internal only
Redis - In-memory cache for Nextcloud. Improves performance by caching frequently accessed data. ❌ Internal only

Optional Services (Commented in docker-compose.yaml)

Service Port Purpose
OpenVPN 943, 1194 VPN server for secure remote access to your home network when you're away.
Mosquitto 1883 MQTT broker for IoT devices. Enables communication between smart home devices.
Homebridge 8581 Exposes non-HomeKit devices to Apple Home app.

πŸ—οΈ Architecture

Internet β†’ Cloudflare (HTTPS) β†’ Cloudflare Tunnel β†’ Traefik β†’ Home Assistant
                                                       ↓
                                               Other Services
  • Cloudflare Tunnel: Provides secure HTTPS access without exposing ports
  • Traefik: Routes traffic to services, adds required headers for Home Assistant
  • Docker Compose: Manages all services as containers

πŸ“‹ Prerequisites

Hardware

  • Raspberry Pi 4 (4GB+ recommended) or any Linux server
  • USB Zigbee adapter (optional, for smart home devices)
  • External storage (optional, for backups)

Software

  • Raspberry Pi OS (64-bit) or Ubuntu Server
  • Docker and Docker Compose
  • Ansible (on your local machine)
  • Terraform (for Cloudflare management)

Accounts

  • Cloudflare account with a domain configured
  • Domain name pointed to Cloudflare nameservers

πŸ”‘ Token & Credential Setup

Before deploying, you'll need to create several tokens and credentials. This section walks you through each one.

1. Cloudflare API Token

The API token allows Terraform to manage your Cloudflare tunnel and DNS records.

Steps to create:

  1. Go to Cloudflare API Tokens

  2. Click "Create Token"

  3. Click "Create Custom Token"

  4. Configure permissions:

    Permission Access
    Zone β†’ DNS Edit
    Account β†’ Cloudflare Tunnel Edit
    Account β†’ Cloudflare Tunnel Read
  5. Under Zone Resources, select: Include β†’ Specific zone β†’ your-domain.com

  6. Under Account Resources, select: Include β†’ Your Account

  7. Click "Continue to summary" β†’ "Create Token"

  8. Copy the token immediately (you won't see it again!)

Where to use it:

# In terraform/terraform.tfvars
cloudflare_api_token = "your-token-here"

2. Cloudflare Account & Zone IDs

These IDs tell Terraform which Cloudflare account and domain to manage.

Finding your Account ID:

  1. Go to Cloudflare Dashboard
  2. The Account ID is in the URL: dash.cloudflare.com/ACCOUNT_ID_HERE/...
  3. Or: Click any domain β†’ scroll down on Overview page β†’ right sidebar shows "Account ID"

Finding your Zone ID:

  1. Go to Cloudflare Dashboard
  2. Click on your domain
  3. On the Overview page, scroll down
  4. Find "Zone ID" in the right sidebar under "API"

Where to use them:

# In terraform/terraform.tfvars
cloudflare_account_id = "your-account-id-here"
cloudflare_zone_id    = "your-zone-id-here"

3. Encryption Key

Used by Homarr to encrypt sensitive data. Generate a random 32-byte hex string.

Generate with OpenSSL:

openssl rand -hex 32

Or with Python:

python3 -c "import secrets; print(secrets.token_hex(32))"

Where to use it:

# In .env
ENCRYPTION_KEY=your-64-character-hex-string-here

4. Grafana Credentials

Default credentials for the Grafana dashboard. Change these in production!

Where to use them:

# In .env
GRAFANA_USER=admin
GRAFANA_PASSWORD=your_secure_password_here

5. Nextcloud Credentials

Nextcloud requires database credentials and an admin account.

Where to use them:

# In .env
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=your_secure_password_here
NEXTCLOUD_DB_PASSWORD=your_db_password_here
NEXTCLOUD_DB_ROOT_PASSWORD=your_db_root_password_here
NEXTCLOUD_TRUSTED_DOMAINS=localhost,192.168.1.100,nextcloud.yourdomain.com

Important: Set NEXTCLOUD_TRUSTED_DOMAINS to include all hostnames/IPs you'll use to access Nextcloud.

Summary: Files to Configure

File Credentials Needed
.env ENCRYPTION_KEY, CLOUDFLARE_TUNNEL_TOKEN, GRAFANA_PASSWORD, NEXTCLOUD_*
terraform/terraform.tfvars cloudflare_api_token, cloudflare_account_id, cloudflare_zone_id, domain
ansible/vars.yml cloudflare_tunnel_token (copied from Terraform output)
ansible/inventory.ini Your server's IP address

πŸš€ Quick Start

1. Clone and Configure

git clone <your-repo-url> hassio-config
cd hassio-config

# Copy sample files
cp sample.env .env
cp ansible/vars.yml.sample ansible/vars.yml
cp terraform/terraform.tfvars.example terraform/terraform.tfvars

# Edit configuration files with your values
vim .env
vim ansible/vars.yml
vim terraform/terraform.tfvars

2. Set Up Cloudflare Tunnel (Terraform)

cd terraform
terraform init
terraform plan
terraform apply

# Get the tunnel token for docker-compose
terraform output -raw tunnel_token
# Add this to your .env file as CLOUDFLARE_TUNNEL_TOKEN
cd ..

3. Deploy to Server (Ansible)

# Update ansible/inventory.ini with your server IP
nano ansible/inventory.ini

# Initial server setup
make initial

# Or step by step:
cd ansible
ansible-playbook 00_main.yml -i inventory.ini --tags initial

4. Access Your Services

Local Access (LAN):

Service URL
Home Assistant http://<server-ip>:8123
Traefik Dashboard ssh -L 8080:localhost:8080 user@server then http://localhost:8080
Portainer ssh -L 9000:localhost:9000 user@server then http://localhost:9000
Homarr ssh -L 7575:localhost:7575 user@server then http://localhost:7575
AdGuard http://<server-ip>:3000
Grafana ssh -L 3001:localhost:3001 user@server then http://localhost:3001
Prometheus ssh -L 9090:localhost:9090 user@server then http://localhost:9090
Nextcloud http://<server-ip>:8082

External Access (via Cloudflare Tunnel):

Enable services in terraform/terraform.tfvars and run make tf-apply:

Service URL Notes
Home Assistant https://hassio.yourdomain.com βœ… Enabled by default
Nextcloud https://nextcloud.yourdomain.com Uncomment in tfvars
Grafana https://grafana.yourdomain.com Uncomment in tfvars
Prometheus https://prometheus.yourdomain.com ⚠️ Has basic auth
AdGuard https://adguard.yourdomain.com Web UI only (DNS stays local)

πŸ“ Project Structure

β”œβ”€β”€ ansible/                    # Server configuration playbooks
β”‚   β”œβ”€β”€ 00_main.yml            # Main orchestration playbook
β”‚   β”œβ”€β”€ inventory.ini          # Server inventory
β”‚   └── vars.yml.sample        # Sample variables
β”œβ”€β”€ terraform/                  # Cloudflare infrastructure
β”‚   β”œβ”€β”€ main.tf                # Tunnel and DNS configuration
β”‚   β”œβ”€β”€ variables.tf           # Input variables
β”‚   └── terraform.tfvars.example
β”œβ”€β”€ traefik/                    # Reverse proxy configuration
β”‚   β”œβ”€β”€ traefik.yml            # Static config
β”‚   └── dynamic/               # Dynamic routing rules
β”‚       β”œβ”€β”€ security.yml       # Security middlewares (NEW)
β”‚       β”œβ”€β”€ hassio.yml         # Home Assistant routing
β”‚       β”œβ”€β”€ nextcloud.yml      # Nextcloud routing
β”‚       β”œβ”€β”€ grafana.yml        # Grafana routing
β”‚       β”œβ”€β”€ prometheus.yml     # Prometheus routing (with auth)
β”‚       └── adguard.yml        # AdGuard routing
β”œβ”€β”€ prometheus/                 # Prometheus monitoring
β”‚   └── prometheus.yml         # Scrape configs
β”œβ”€β”€ grafana/                    # Grafana dashboards
β”‚   └── provisioning/          # Auto-provisioned datasources
β”œβ”€β”€ nextcloud/                  # Nextcloud cloud storage
β”‚   β”œβ”€β”€ config/                # Nextcloud configuration
β”‚   └── custom_apps/           # Custom Nextcloud apps
β”œβ”€β”€ homeassistant-config/       # Home Assistant configuration
β”‚   └── configuration.yaml
β”œβ”€β”€ docker-compose.yaml         # Service definitions
β”œβ”€β”€ Makefile                    # Convenient command shortcuts
β”œβ”€β”€ SECURITY.md                 # Security documentation (NEW)
└── sample.env                  # Environment template

πŸ”§ Make Commands

make help              # Show all available commands

# Ansible commands
make deploy            # Deploy configs and restart services
make initial           # Initial server setup
make pull              # Backup configs from server
make check             # Dry run (preview changes)

# Terraform commands
make tf-init           # Initialize Terraform
make tf-plan           # Preview infrastructure changes
make tf-apply          # Apply infrastructure changes
make tf-token          # Output tunnel token
make tf-update-env     # Update .env with tunnel token

# Utility commands
make status            # Check container status
make logs              # View all container logs
make ssh               # SSH to server

πŸ” Security

Full documentation: See SECURITY.md for threat model, network architecture, and detailed security controls.

Quick Security Overview

This homelab implements production-grade security practices:

Control Implementation
Docker Socket Protection Proxy with read-only, filtered access
Network Segmentation Three networks: edge, app, internal
Container Hardening cap_drop: ALL, read_only, no-new-privileges
Admin UI Protection Bound to localhost, SSH tunnel required
Rate Limiting Applied to all external endpoints
Security Headers HSTS, XSS protection, CSP
Secrets Management Environment files, git-ignored
Pinned Images All images use specific versions (no :latest)

Services Never to Expose Publicly

⚠️ These services should NEVER be accessible from the internet:

Service Reason
Prometheus No authentication by default
Portainer Full Docker control
Traefik Dashboard Infrastructure information
Node Exporter System metrics
cAdvisor Container metrics
Databases (MariaDB, Redis) Data access

To access these services securely, use SSH port forwarding:

# Access multiple admin services at once
ssh -L 9090:localhost:9090 \
    -L 9000:localhost:9000 \
    -L 8080:localhost:8080 \
    user@your-server

Recommended: Enable Cloudflare Access

For production use, enable Cloudflare Access to add identity-based authentication (Google, GitHub, etc.) in front of all exposed services. This provides:

  • Zero-trust authentication
  • Multi-factor authentication
  • Session management
  • Audit logging

🏒 Production Parallels

This homelab isn't just a toyβ€”it mirrors real-world platform engineering practices. Here's how these patterns translate to enterprise infrastructure:

Least Privilege

Homelab Production
cap_drop: ALL + specific cap_add AWS IAM least privilege policies
docker-socket-proxy (read-only) Service accounts with minimal permissions
Non-root containers (user: 65534) Rootless containers in Kubernetes
no-new-privileges: true PodSecurityPolicies / SecurityContextConstraints

Infrastructure as Code (IaC)

Homelab Production
Terraform for Cloudflare Terraform for AWS/GCP/Azure
Ansible playbooks Ansible Tower / AWX
docker-compose.yaml Kubernetes manifests / Helm charts
Git-ignored secrets in .env HashiCorp Vault, AWS Secrets Manager

Zero Trust Networking

Homelab Production
Cloudflare Tunnel (no open ports) Service mesh (Istio, Linkerd)
Network segmentation (edge/app/internal) VPC subnets, security groups
SSH tunnels for admin access Bastion hosts, VPN, AWS SSM
Cloudflare Access Identity-aware proxy (IAP), Okta

Defense in Depth

Homelab Production
Multiple auth layers (Cloudflare + app) WAF + API Gateway + app auth
Rate limiting middleware AWS WAF rate limiting, API throttling
Security headers (HSTS, CSP) Same, plus additional WAF rules
Separate networks per tier VPC peering, Transit Gateway

Observability

Homelab Production
Prometheus + Grafana Datadog, New Relic, or self-hosted
Traefik access logs ELK stack, Splunk
Container metrics (cAdvisor) Container Insights, Prometheus Operator
Structured JSON logging Same, with correlation IDs

Supply Chain Security

Homelab Production
Pinned image versions Image signing (Cosign, Notary)
No :latest tags Immutable tags, digest pinning
Official images only Private registry, vulnerability scanning

Key Takeaways for Your Resume

If you've deployed and understand this homelab, you can speak to:

  1. Container security - Runtime hardening, capability management, privilege restrictions
  2. Network architecture - Segmentation, zero-trust principles, reverse proxy patterns
  3. Infrastructure as Code - Terraform, Ansible, declarative configuration
  4. Secrets management - Environment isolation, rotation strategies
  5. Observability - Metrics collection, visualization, log aggregation
  6. Incident response - Log locations, audit trails, recovery procedures

πŸ“– Documentation


πŸ”Œ Adding More Services

Pre-configured Services

The following services already have Traefik routes configured in traefik/dynamic/:

Service Traefik Config Just Enable in Terraform
Nextcloud nextcloud.yml βœ… Ready
Grafana grafana.yml βœ… Ready
Prometheus prometheus.yml ⚠️ Basic auth required
AdGuard adguard.yml βœ… Ready (Web UI only)

To enable external access, uncomment the service in terraform/terraform.tfvars and run:

make tf-apply
make deploy

Adding a New Service

1. Add to docker-compose.yaml

  myservice:
    image: myservice/image:1.0.0  # Always pin version!
    container_name: myservice
    restart: unless-stopped
    # Security hardening
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    # Bind to localhost for admin services
    ports:
      - "127.0.0.1:8000:8000"
    networks:
      - app  # or internal for backend services

2. Add Traefik Route (for external access)

Create traefik/dynamic/myservice.yml:

http:
  routers:
    myservice:
      rule: "Host(`myservice.yourdomain.com`)"
      service: myservice
      entrypoints:
        - web
      middlewares:
        - external-secure@file  # Apply security middlewares
      priority: 10

  services:
    myservice:
      loadBalancer:
        servers:
          - url: "http://myservice:8000"

3. Add Cloudflare DNS (Terraform)

In terraform/terraform.tfvars:

additional_services = {
  myservice = {
    subdomain   = "myservice"
    service_url = "http://traefik:80"
    enabled     = true
  }
}

4. Deploy

make tf-apply    # Update Cloudflare
make deploy      # Deploy to server

πŸ› Troubleshooting

Services not accessible externally

  1. Check Cloudflare tunnel status in dashboard
  2. Verify tunnel token in .env matches Terraform output
  3. Check cloudflared logs: make logs or docker compose logs cloudflared

Home Assistant login issues

  1. Verify configuration.yaml has trusted proxies configured
  2. Check Traefik is adding X-Forwarded-Proto header
  3. Clear browser cookies and try again

Container won't start

make status              # Check container status
docker compose logs -f   # View logs
docker compose config    # Validate compose file

Permission denied errors

If you see permission errors after hardening:

  1. Check the container's required user/group
  2. Verify volume ownership matches container user
  3. Some containers need specific capabilities - check logs

Accessing admin services

Admin services (Prometheus, Portainer, etc.) are bound to localhost. Use SSH tunnels:

# Example: Access Prometheus
ssh -L 9090:localhost:9090 user@your-server
# Then open http://localhost:9090 in your browser

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

Security issues should be reported privately - see SECURITY.md.


πŸ“„ License

MIT License - See LICENSE for details.


πŸ™ Acknowledgments

About

Generic template for setting up a homelab

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors