Infrastructure-as-code for my homelab. Provisions servers on Hetzner Cloud with OpenTofu and configures a HashiCorp Nomad cluster using Ansible.
Hetzner Cloud (nbg1)
+---------------------------------------------------------------------+
| |
| Network: "main" (10.0.0.0/16) |
| +---------------------------------------------------------------+ |
| | | |
| | Subnet: "cloud01" (10.0.1.0/24, eu-central) | |
| | | |
| | +------------+ +------------+ +------------+ +--------+ | |
| | | cx3301 | | cx3302 | | cx3303 | | cx3304 | | |
| | | Nomad | | Nomad | | Nomad | | Nomad | | |
| | | Server | | Client | | Client | | Client | | |
| | | 10.0.1.1 | | 10.0.1.2 | | 10.0.1.3 | |10.0.1.4| | |
| | +-----+------+ +-----+------+ +-----+------+ +---+----+ | |
| | | | | | | |
| +---------|---------------|---------------|--------------|-------+ |
| | | | | |
+---------------------------------------------------------------------+
|
+--------------+---------------+
| Firewall: "firewall01" |
| SSH + Nomad from home IP |
| All traffic within subnet |
+--------------+---------------+
|
+-------+--------+
| Home IP |
+----------------+
Four cx33 servers running Ubuntu 24.04 in Nuremberg. One Nomad server node, three Nomad client nodes with Docker as the task driver. A firewall restricts external access to SSH (22) and Nomad (4646) from your home IP, while allowing all internal traffic within the subnet.
- uv -- Python package manager
- OpenTofu ~> 1.11
- direnv -- automatic environment variable loading
- An SSH keypair at
~/.ssh/id_ed25519 - A Hetzner Cloud account with an API token
- A GitHub account with a personal access token for GHCR (GitHub Container Registry)
git clone git@github.com:ubiquitousbyte/homelab.git
cd homelabCopy the example files and fill in your values:
cp .envrc.example .envrc
cp .env.example .envEdit .envrc and replace the placeholder values with your actual credentials. Edit .env and set your home IP address.
Then allow direnv to load the environment:
direnv allowI use the 1Password CLI (op) to inject secrets into .envrc rather than hardcoding them. For example:
export HCLOUD_TOKEN=$(op read "op://Vault/Item/credential")This is optional -- you can set the values directly in .envrc if you prefer a different approach.
uv syncuv run ansible-galaxy install -r requirements.ymluv run pre-commit installtofu init
tofu plan
tofu applyuv run ansible-playbook playbooks/site.ymlThis project uses pre-commit to run checks locally before each commit:
- Whitespace and formatting -- trailing whitespace, end-of-file newlines, YAML syntax
- Security -- private key detection, large file prevention, IaC security scanning (Checkov)
- Ansible -- ansible-lint with the
sharedprofile - OpenTofu --
tofu fmtformatting check - Shell -- ShellCheck for any shell scripts
To run all hooks manually:
uv run pre-commit run --all-filesGitHub Actions runs the same pre-commit hooks on every push to main and on pull requests, plus a separate OpenTofu validation step (tofu init + tofu validate).