Skip to content

Repository layout

homelab-infrastructure/
├── stage0/                       # Terraform: optional cloud machines, off by default
│   ├── main.tf                   # one module block per account, since providers cannot be iterated
│   ├── variables.tf              # one account map per provider
│   ├── providers.tf              # required_providers and one aliased oci provider per account
│   ├── outputs.tf                # re-exports the module outputs so `terraform output` sees them
│   ├── backend.tf                # Terraform Cloud, the single homelab-stage0 workspace
│   └── oci-freetier/             # Oracle Always Free A1 provider module
├── stage1/                       # Ansible: host setup and Kubernetes bootstrap
│   ├── ansible.cfg               # local_tmp is set to /tmp, see below
│   ├── site.yml                  # the six plays
│   ├── inventories/
│   │   └── inventory.yml         # host groups and every component version pin
│   ├── roles/                    # 10 roles
│   ├── handlers/                 # 6 handler files, imported per play
│   ├── cloud-init/raspberry-pi/  # worker bootstrap examples
│   ├── tests/                    # test_upgrade_ordering.py
│   └── requirements*.txt|yml     # Python and Ansible Galaxy dependencies
├── stage2/                       # Terraform: the platform
│   ├── main.tf                   # module orchestration and the depends_on DAG
│   ├── variables.tf              # every input variable, ~38 KB
│   ├── output.tf
│   ├── providers.tf
│   ├── backend.tf                # Terraform Cloud
│   └── <module>/                 # 20 module directories
├── docs/                         # this site
│   ├── requirements.txt          # the MkDocs pin
│   └── stylesheets/extra.css
├── scripts/                      # helper scripts
│   ├── sync-versions.sh          # version drift gate
│   ├── check-docs.py             # docs coverage, citation and nav gate
│   ├── tests/                    # self-tests for the scripts in this directory
│   ├── oci-apply-retry.sh        # retries stage0 apply while Oracle is out of capacity
│   ├── bump-versions.sh
│   ├── docker-build.sh, docker-run.sh, repo-setup.sh
│   └── container/root/           # files baked into the image
├── mkdocs.yml
├── Dockerfile                    # tool versions live in ARG statements
├── Taskfile.yml                  # the command interface
├── .pre-commit-config.yaml
├── .gitlab-ci.yml                # build, scan, docs gate
├── .github/workflows/            # lint, container scan, docs publish, version sync
└── .env.example                  # only the two Bitwarden values

Files that are not where you would expect

File Note
Module documentation Under docs/stage2/ and docs/stage0/, not in the module directories. The directories hold .tf only.
Version pins Split across Dockerfile and stage1/inventories/inventory.yml. See Version pins.
AGENTS.md Stays at the repo root, with CLAUDE.md, AGENT.md, GEMINI.md, .cursorrules and .github/copilot-instructions.md as symlinks to it. Mirrored onto this site.
CONTRIBUTING.md, SECURITY.md Stay at the root, where GitHub's community-profile features read them. Mirrored onto this site.
container/root/ Gitignored. Where the fetched kubeconfig lands and what the tooling container mounts.

stage1/ansible.cfg sets local_tmp = /tmp/.ansible/tmp

The container bind-mounts /root from the Docker host, and fcntl.flock is not enforced on that filesystem. Unenforced flock breaks the AnsiballZ module-cache mutex, so concurrent workers race on a shared -part file and one loses with ENOENT. Moving controller-side temp work to /tmp puts it on a filesystem where flock works.

Generated and ignored

site/, .venv-docs/, .task/, container/root, .env, **/.terraform, *.tfvars, **/terraform.tfstate, *.bak.