Skip to content

Repository files navigation

Remote DevOps Portfolio

A real, self-hosted GitOps Kubernetes platform running on my own Proxmox hardware — Terraform → Ansible → K3s → ArgoCD, with Sealed Secrets, Prometheus/Alertmanager alerting to my phone, and a CI pipeline that validates every manifest before it reaches the cluster. Every bug hit along the way is documented with its root cause, not just the fix. Started as a cloud DevOps portfolio project on Oracle Cloud Infrastructure (still below); the homelab is now the main, actively-developed part.

Validate GitOps manifests License: MIT Last commit

📄 Overview for recruiters — what this is and why it exists, in plain language. 📄 Full setup & runbook — architecture diagram, step-by-step replication guide, and every bug hit along the way (with root causes, not just fixes).


Tech Stack

  • Cloud Provider: Oracle Cloud Infrastructure (OCI)
  • Virtualization (Homelab): Proxmox VE, provisioned via the bpg/proxmox Terraform provider
  • Infrastructure as Code: Terraform
  • Configuration Management: Ansible (+ Ansible Vault for secrets)
  • Containerization: Docker & Docker Compose
  • Container Orchestration: K3s (lightweight Kubernetes)
  • Distributed Block Storage: Longhorn
  • GitOps: ArgoCD
  • Traffic Routing & SSL: Traefik / Nginx Proxy Manager
  • Monitoring & Management: Portainer, Uptime Kuma, Watchtower (Docker layer) · kube-prometheus-stack (Kubernetes layer, GitOps-deployed)

Project Roadmap

Phase 1: Foundation & Security

Focus: Establish an indestructible baseline on the "Always Free" tier.

  • Account Hardening: Enabled MFA for OCI login and set a budget alert (> €1.00).
  • Base Instance: Provisioned an "Ampere A1" instance (ARM) with 4 Cores, 24 GB RAM, and Ubuntu.
  • Cloud Network (VCN): Configured Ingress Rules (opened ports 80 & 443).
  • Docker Setup: Installed Docker, Docker Compose, and Portainer for management (via the docker_setup / deploy_compose Ansible roles).

Phase 2: Infrastructure as Code

Focus: Learn automation and resource management.

  • Terraform Basics: Local setup and API keys configured via .oci/config.
  • The Sandbox: Wrote scripts to automatically provision and destroy x86 test servers (terraform apply & terraform destroy).
  • Version Control: Established a secure GitHub repository (including strict .gitignore to protect tfstate files and credentials).

Phase 3: Traffic & Automation

Focus: Professional traffic routing and container updates.

  • Reverse Proxy: Nginx Proxy Manager running as a Docker container, deployed via the nginx_proxy Ansible role.
  • Domain Routing: Configured DNS records and Let's Encrypt for automatic SSL certificates.
  • Watchtower: Implemented automated update management for running containers.

Phase 4: Showcase Portfolio

Focus: Deploy visible applications that utilize the server's resources.

  • Monitoring: Deployed Uptime Kuma to monitor cloud services and the local homelab.
  • Workspaces: Deployed Kasm in Docker to leverage the 24 GB RAM of the ARM instance for desktop streaming.
  • Clean-Up: Ensured all manual and Terraform-generated x86 resources are completely destroyed.

Phase 5: Homelab Kubernetes Platform (Proxmox + K3s)

Focus: Stand up a self-hosted, GitOps-managed Kubernetes platform on bare-metal Proxmox — "cattle, not pets" for the homelab.

  • Proxmox IaC: Migrated to the bpg/proxmox Terraform provider; modular ubuntu-vm module, for_each-driven, provisions a 3-node K3s cluster (1 control-plane + 2 workers) from a single vms map.
  • Cloud-Init: Static IPs, injected SSH public key, and the QEMU guest agent enabled on every node.
  • Dynamic Ansible Inventory: Terraform generates ansible/inventories/production/hosts.ini with [control_plane] / [workers] groups straight from node role, ready for K3s bootstrapping playbooks.
  • K3s Cluster Bootstrap: ansible/playbooks/install-k3s.yml installs the K3s server on the control-plane, retrieves its node token, and joins both workers as agents.
  • Distributed Block Storage: Longhorn, backed by a Terraform-provisioned secondary disk (scsi1, 50GB) attached to worker nodes only; formatted, and mounted at /var/lib/longhorn via ansible/playbooks/setup-longhorn-nodes.yml.
  • GitOps Bootstrap: ArgoCD installed via kubernetes/bootstrap/install-argocd.sh.
  • First GitOps App: kube-prometheus-stack deployed as an ArgoCD Application (kubernetes/apps/kube-prometheus-stack.yaml), with Prometheus, Alertmanager, and Grafana PVCs all backed by Longhorn — proving persistent, replicated storage survives pod/node loss end to end.
  • K3s Ingress: K3s ships with Traefik by default, but it isn't yet configured/exposed for the new monitoring namespace or future apps.

Phase 6: Production Hardening & GitOps Maturity (Next Steps)

Focus: close the gap between "it works" and "it's operable." Not yet implemented — a candidate list for what's next.

  • App-of-Apps pattern: kubernetes/apps/root-app.yaml now watches kubernetes/apps/ (non-recursive) directly. Every future top-level .yaml under that directory is auto-registered on git push — no more one-time manual kubectl apply -f per app.
  • Remote Terraform State: terraform.tfstate is still local-only — no locking, no team-safety, no recovery if the machine running terraform apply is lost.
  • Longhorn Backups: No S3/NFS backup target is configured — losing more worker VMs than Longhorn's replica count would still mean real data loss. Blocked on NAS arrival.
  • GitOps Secrets Management: Sealed Secrets controller deployed via ArgoCD; Longhorn UI's BasicAuth and Tandoor's DB credentials now live in git as SealedSecret objects, not created out-of-band. Immich's and BookStack's secrets are not yet migrated — see runbook.
  • CI Validation: .github/workflows/validate-gitops.yml runs yamllint, kubectl kustomize on every overlay, and kubeconform schema validation against upstream Kubernetes' OpenAPI schemas on every push/PR to main. Terraform/Ansible linting is still not covered.
  • Alerting: Alertmanager's default receiver now posts to an ntfy-alertmanager bridge in-cluster, which forwards to a private ntfy.sh topic with severity mapped to priority/tags. See runbook for the topic name.
  • K8s Ingress & TLS: Decide between K3s's built-in Traefik and the existing Docker-based Nginx Proxy Manager for exposing Grafana etc. externally, then wire up cert-manager or NPM accordingly.

Folder Structure

remote-devops-lab/
├── .gitignore
├── README.md
│
├── terraform/
│   └── proxmox/
│       ├── main.tf
│       ├── providers.tf                 <-- bpg/proxmox provider
│       ├── variables.tf
│       ├── outputs.tf
│       ├── terraform.tfvars             <-- gitignored, holds real credentials + the vms map
│       ├── terraform.tfvars.example
│       ├── inventory.tftpl              <-- renders the Ansible inventory below
│       └── modules/
│           └── ubuntu-vm/               <-- one K3s node per for_each entry; optional Longhorn data disk
│               ├── main.tf
│               ├── variables.tf
│               ├── outputs.tf
│               └── providers.tf
│
├── ansible/
│   ├── ansible.cfg                      <-- Basic ansible settings
│   ├── inventories/
│   │   └── production/
│   │       ├── hosts.ini                <-- AUTO-GENERATED by Terraform! Do not edit manually.
│   │       └── group_vars/
│   │           ├── all.yml
│   │           ├── docker_nodes.yml     <-- Variables for all docker hosts
│   │           └── proxy_nodes.yml
│   │
│   ├── group_vars/
│   │   └── all/
│   │       └── vault.yml                <-- Ansible Vault-encrypted secrets (sudo password, etc.)
│   │
│   ├── playbooks/
│   │   ├── site.yml                     <-- Master playbook (Docker/proxy nodes)
│   │   ├── docker.yml                   <-- Maps docker role to VMs
│   │   ├── proxy.yml                    <-- Maps proxy role to VMs
│   │   ├── install-k3s.yml              <-- Bootstraps the K3s cluster itself (server + agent join)
│   │   └── setup-longhorn-nodes.yml     <-- Preps K3s workers' secondary disk for Longhorn
│   │
│   └── roles/
│       ├── system_setup/                <-- Root/LVM partition growth, disk pre-flight checks
│       │   └── tasks/main.yml
│       ├── docker_setup/                <-- Installs docker engine
│       │   └── tasks/main.yml
│       ├── deploy_compose/              <-- Generic role to copy and run compose files
│       │   └── tasks/main.yml
│       └── nginx_proxy/
│           └── tasks/main.yml
│
├── kubernetes/                          <-- GitOps layer for the K3s cluster
│   ├── bootstrap/
│   │   └── install-argocd.sh            <-- One-time ArgoCD install onto K3s
│   └── apps/
│       └── kube-prometheus-stack.yaml   <-- ArgoCD Application; Prometheus/Grafana PVCs on Longhorn
│
└── docker/
    ├── portainer/
    │   └── docker-compose.yml
    ├── nginx-proxy-manager/
    │   └── docker-compose.yml
    └── monitoring/
        ├── docker-compose.yml
        └── prometheus.yml               <-- Docker-Compose-based Prometheus (separate from the K8s stack above)

About

Infrastructure as Code (IaC) repository for my Remote DevOps portfolio. Features automated OCI provisioning with Terraform, container management, and secure traffic routing.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages