{"id":26038408,"url":"https://github.com/ryanmalonzo/homelab","last_synced_at":"2026-04-30T16:33:35.551Z","repository":{"id":278726184,"uuid":"936300525","full_name":"ryanmalonzo/homelab","owner":"ryanmalonzo","description":"Babylon, my personal homelab orchestrated using Docker Compose 🐳 and GitOps 🚀","archived":false,"fork":false,"pushed_at":"2025-03-03T18:55:54.000Z","size":73,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-03T19:39:21.032Z","etag":null,"topics":["devops","docker","docker-compose","gitops","homelab","komodo","lxc","proxmox","proxmox-ve","selfhosted","sysadmin","tailscale"],"latest_commit_sha":null,"homepage":"https://ryanmalonzo.com","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanmalonzo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-20T21:23:12.000Z","updated_at":"2025-03-03T18:55:57.000Z","dependencies_parsed_at":"2025-03-03T19:49:30.209Z","dependency_job_id":null,"html_url":"https://github.com/ryanmalonzo/homelab","commit_stats":null,"previous_names":["ryanmalonzo/babylon","ryanmalonzo/homelab"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmalonzo%2Fhomelab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmalonzo%2Fhomelab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmalonzo%2Fhomelab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmalonzo%2Fhomelab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanmalonzo","download_url":"https://codeload.github.com/ryanmalonzo/homelab/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242369158,"owners_count":20116606,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","docker","docker-compose","gitops","homelab","komodo","lxc","proxmox","proxmox-ve","selfhosted","sysadmin","tailscale"],"created_at":"2025-03-07T09:42:35.619Z","updated_at":"2026-04-30T16:33:35.546Z","avatar_url":"https://github.com/ryanmalonzo.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# chaldea\n\n![healthchecks.io](https://healthchecks.io/badge/261911a0-3c4a-4c11-b500-6330b0/8-eeEdAn-2.svg)\n\nMy self-deploying homelab infrastructure using NixOS and Terraform. Automated backups, continuous deployment, monitoring, version control.\n\n## Structure\n\n```\n.\n├── .github/workflows/\n│   ├── ci.yaml\n│   ├── cd.yaml\n│   └── ...\n├── modules/\n│   ├── backups.nix         # restic to backblaze b2\n│   ├── caddy.nix\n│   ├── dns.nix\n│   ├── networking.nix\n│   ├── virtualisation.nix\n│   ├── zfs.nix\n│   └── ...\n├── services/               # individual service definitions\n├── terraform/              # cloudflare dns records\n├── secrets/                # sops-encrypted secrets\n├── configuration.nix\n├── flake.nix\n└── hardware-configuration.nix\n```\n\n## Tech stack\n\n- **NixOS** with flakes — declarative system configuration\n- **Podman** — container runtime\n- **ZFS** — storage pool\n- **Terraform** — infrastructure as code for DNS records\n- **sops-nix** — encrypted secrets in version control\n- **GitHub Actions** — CI/CD using self-hosted runner\n\n## Deployment workflow\n\n\u003e The GitHub Actions runner runs on chaldea and deploys to itself. The host rebuilds its own configuration on every merge. Zero manual deployments.\n\n### Cloud providers\n\n- **Backblaze B2** — backup storage for restic\n- **Tailscale** — VPN mesh network\n- **Pangolin** — self-hosted VPS (unmanaged)\n- **Cloudflare** — DNS management via Terraform\n- **Healthchecks.io** —  heartbeat monitor for Gatus\n\n### CI/CD pipeline\n\nThe GitHub Actions runner executes **on chaldea itself**.\n\n1. Pull requests trigger validation: formatting checks, flake correctness, and terraform plan\n2. Terraform plan output is posted as a PR comment when DNS changes are detected\n3. Merges to `main` trigger deployment: DNS updates followed by system configuration\n4. PRs labeled with `skip-deploy` bypass the deployment step\n\n**The entire system state is version controlled.**\n\n## Adding a new service\n\n### Internal service (behind Tailscale)\n\n**1. Create `services/\u003cname\u003e.nix`**\n\n```nix\n{ ... }:\n\n{\n  systemd.tmpfiles.rules = [\n    \"d /srv/\u003cname\u003e/data 0755 1000 100 -\"\n  ];\n\n  virtualisation.oci-containers.containers.\u003cname\u003e = {\n    image = \"image/name:tag\";\n    networks = [ \"\u003cname\u003e\" ];\n    ports = [ \"\u003chost-port\u003e:\u003ccontainer-port\u003e\" ];\n    volumes = [\n      \"/srv/\u003cname\u003e/data:/data\"\n    ];\n    environment = {\n      TZ = \"Europe/Paris\";\n    };\n  };\n\n  systemd.services.\"podman-\u003cname\u003e\".after = [ \"podman-network-\u003cname\u003e.service\" ];\n  systemd.services.\"podman-\u003cname\u003e\".requires = [ \"podman-network-\u003cname\u003e.service\" ];\n}\n```\n\n**2. Add the network — `modules/container-networks.nix`**\n\n```nix\n  networks = [\n    ...\n    \"\u003cname\u003e\"\n  ];\n```\n\n**3. Add the Caddy virtual host — `modules/caddy.nix`**\n\n```nix\nvirtualHosts = {\n  ...\n  \"\u003cname\u003e.internal.chaldea.dev\" = {\n    extraConfig = ''\n      ${tlsConfig}\n      reverse_proxy localhost:\u003chost-port\u003e\n    '';\n  };\n};\n```\n\n**4. Import the module — `flake.nix`**\n\n```nix\nmodules = [\n  ...\n  ./services/\u003cname\u003e.nix\n];\n```\n\n### Public service\n\n**1. Create `services/\u003cname\u003e.nix`**\n\n```nix\n{ ... }:\n\n{\n  virtualisation.oci-containers.containers.\u003cname\u003e = {\n    image = \"image/name:tag\";\n    networks = [ \"proxy\" ];\n  };\n\n  systemd.services.\"podman-\u003cname\u003e\".after = [ \"podman-network-proxy.service\" ];\n  systemd.services.\"podman-\u003cname\u003e\".requires = [ \"podman-network-proxy.service\" ];\n}\n```\n\n**2. Add a CNAME record — `terraform/main.tf`**\n\n```hcl\n  cname_subdomains = [\n    ...\n    \"\u003cname\u003e\"\n  ]\n```\n\n**3. Import the module — `flake.nix`**\n\n```nix\nmodules = [\n  ...\n  ./services/\u003cname\u003e.nix\n];\n```\n\n### Adding a sops secret\n\n**1. Add the plaintext value to `secrets/secrets.yaml`**\n\n```sh\nsops secrets/secrets.yaml\n```\n\n**2. Reference the secret in the service**\n\n```nix\n{ config, ... }:\n\n{\n  sops.secrets.\u003csecret-name\u003e = { };\n\n  sops.templates.\"\u003cname\u003e-env\" = {\n    content = ''\n      ENV_VAR=${config.sops.placeholder.\u003csecret-name\u003e}\n    '';\n  };\n\n  virtualisation.oci-containers.containers.\u003cname\u003e = {\n    ...\n    environmentFiles = [ config.sops.templates.\"\u003cname\u003e-env\".path ];\n  };\n\n  # Restart the container when the secret changes\n  systemd.services.\"podman-\u003cname\u003e\".restartTriggers = [\n    config.sops.templates.\"\u003cname\u003e-env\".file\n  ];\n}\n```\n\n### Adding a Gatus monitor\n\nAdd an endpoint to the `endpoints` list in `services/gatus.nix`:\n\n```nix\nendpoints = [\n  ...\n  {\n    name = \"\u003cName\u003e\";\n    group = \"\u003cgroup\u003e\";\n    url = \"https://\u003cname\u003e.internal.chaldea.dev\";\n    interval = \"30s\";\n    conditions = [ \"[STATUS] == 200\" ];\n    alerts = [ { type = \"ntfy\"; } ];\n  }\n];\n```\n\n### GitHub configuration\n\nSecrets and variables configured at the repository level:\n\n| Name                        | Type     | Description                                 | Example                               |\n| --------------------------- | -------- | ------------------------------------------- | ------------------------------------- |\n| `AWS_ACCESS_KEY_ID`         | Secret   | Backblaze B2 access key for terraform state | `0001a2b3c4d5e6f7g8h9`                |\n| `AWS_SECRET_ACCESS_KEY`     | Secret   | Backblaze B2 secret key for terraform state | `K001abcdefghijklmnopqrstuvwxyz`      |\n| `CLOUDFLARE_API_TOKEN`      | Secret   | Cloudflare API token for DNS management     | `abc123...`                           |\n| `SSH_PRIVATE_KEY`           | Secret   | Ed25519 private key for deployment          | `-----BEGIN OPENSSH PRIVATE KEY-----` |\n| `TF_VAR_cloudflare_zone_id` | Variable | Cloudflare zone ID for DNS records          | `abc123def456ghi789jkl012mno345`      |\n| `TF_VAR_tailscale_ip`       | Variable | Tailscale IP address of chaldea             | `100.x.y.z`                           |\n| `TF_VAR_pangolin_ip`        | Variable | Public IP of pangolin VPS                   | `1.2.3.4`                             |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanmalonzo%2Fhomelab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanmalonzo%2Fhomelab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanmalonzo%2Fhomelab/lists"}