# Homelab Infrastructure as Code A Pulumi-based IaC template for managing a Proxmox homelab. The goal is to replace manual GUI configuration and ad-hoc YAML stacks (LXC, VM, Docker, etc.) with version-controlled, reproducible infrastructure — starting with a highly available k3s cluster across multiple Proxmox nodes. This repo is intentionally abstract: credentials are never hardcoded, making it easy to fork and adapt as a template for your own homelab. ## Why IaC for a homelab? - **Reproducibility** — rebuild your entire environment from scratch with a single command - **Version history** — every change is tracked in git; roll back at any time - **Auditability** — diff infrastructure changes before applying them (`pulumi preview`) - **Automation** — CI/CD handles deploys; no manual SSH into nodes - **Portability** — swap node names, datastores, or credentials without touching logic ## Repository layout ``` . ├── proxmox-infra/ # Pulumi TypeScript stack — VMs & LXC on Proxmox │ ├── index.ts # All Pulumi resources │ ├── Pulumi.yaml # Stack project definition │ └── Pulumi.dev.yaml # Encrypted stack config (gitignored) ├── .gitea/ │ └── workflows/ │ └── deploy-proxmox-infra.yaml # Gitea Actions CI/CD pipeline ``` ## Current stack: `proxmox-infra` Provisions a 5-node k3s cluster spread across two Proxmox hosts (`pve` and `pve-bckp`, third bare metal host to be added later for actual parity): | VM name | Role | Proxmox node | | ------------ | ------ | ------------ | | k3s-master-1 | master | pve | | k3s-master-2 | master | pve | | k3s-worker-1 | worker | pve | | k3s-master-3 | master | pve-bckp | | k3s-worker-2 | worker | pve-bckp | Each node is a full clone of an Ubuntu Noble (24.04) cloud-image template, with cloud-init injecting hostname, user credentials, and SSH key at boot. **Tech stack:** - [Pulumi](https://www.pulumi.com/) with TypeScript - [`@muhlba91/pulumi-proxmoxve`](https://github.com/muhlba91/pulumi-provider-proxmoxve) v8.x community provider - Self-hosted Pulumi state backend (PostgreSQL) - Gitea Actions for CI/CD ## Prerequisites - [Pulumi CLI](https://www.pulumi.com/docs/install/) installed - Node.js 18+ and npm - Access to a Proxmox node with an API token - A self-hosted Pulumi state backend (PostgreSQL connection string) - Gitea instance for CI/CD (optional for local use) ## Getting started ### 1. Clone and install ```bash git clone cd proxmox-infra npm install ``` ### 2. Configure credentials All secrets are stored as encrypted Pulumi config values — never in plain environment variables or committed files. ```bash # Set Proxmox API credentials pulumi config set --secret pve1Endpoint https://:8006 pulumi config set --secret pve1ApiToken @pam!= pulumi config set --secret pve2Endpoint https://:8006 pulumi config set --secret pve2ApiToken @pam!= # Set VM credentials pulumi config set --secret k3sVmPassword pulumi config set --secret sshPvePublicKey "ssh-ed25519 AAAA..." ``` Pulumi encrypts these values into `Pulumi.dev.yaml` using your `PULUMI_CONFIG_PASSPHRASE`. ### 3. Set the state backend ```bash export PULUMI_BACKEND_URL=postgresql://:@/ export PULUMI_CONFIG_PASSPHRASE= ``` ### 4. Preview and deploy ```bash # See what will change before touching anything pulumi preview # Sync Pulumi state with actual Proxmox state (run after any manual GUI changes) pulumi refresh --yes # Deploy pulumi refresh --yes && pulumi up --yes ``` ## CI/CD (Gitea Actions) The workflow at `.gitea/workflows/deploy-proxmox-infra.yaml` runs automatically: | Event | Action | | --------------------- | ------------------------------ | | Pull request → `main` | `pulumi preview` (read-only) | | Push to `main` | `pulumi refresh` + `pulumi up` | | Manual trigger | `pulumi refresh` + `pulumi up` | ### Required Gitea secrets Configure these under **Settings → Actions → Secrets** in your Gitea repo: | Secret | Description | | -------------------------- | -------------------------------------------------- | | `PULUMI_BACKEND_URL` | PostgreSQL connection string for the state backend | | `PULUMI_CONFIG_PASSPHRASE` | Passphrase to decrypt secrets in `Pulumi.dev.yaml` | | `PULUMI_DEV_YAML` | Base64-encoded content of `Pulumi.dev.yaml` | `Pulumi.dev.yaml` is gitignored because it contains your encryption salt. Whenever it changes (e.g. after adding or rotating a secret), re-encode it and paste the output into the Gitea secret: ```bash base64 -w 0 proxmox-infra/Pulumi.dev.yaml ``` ## Adapting this as a template 1. Fork or copy the repo 2. Update node names (`pve`, `pve-bckp`) and datastore IDs in `index.ts` to match your setup 3. Add or remove VMs from the `nodeConfigs` array 4. Set your own secrets with `pulumi config set --secret` 5. Point the CI/CD workflow at your own Git instance ## Roadmap - LXC container management - Docker / Compose stack provisioning - Network and firewall rules - Automated k3s bootstrapping (kubeconfig export) - Additional worker nodes and storage volumes - Migrate secrets management to [OpenBao](https://openbao.org/) — replace `PULUMI_CONFIG_PASSPHRASE` and manual `Pulumi.dev.yaml` encoding with a self-hosted vault - Add a third bare metal proxmox instance to create an actual 3 node parity.