Cloud Automation: From Manual Tweaks to Infrastructure as Code
Achieving Repeatable Infrastructure with Terraform and Ansible
In a previous post, I described the architecture for my Ollama AWS hybrid deployment - running LLM inference on my local hardware while exposing a web interface via an EC2 instance. At the time, the setup was largely manual. I'd spin up an instance in the AWS console and manually configure Docker.
At the end, I mentioned that Terraform was on my roadmap. The goal wasn't just to move parts of the stack to the cloud, but to treat the entire lifecycle, from VPC provisioning to application deployment, as code. I can gladly say that the goal has been achieved, sparing me hours of back-breaking labor (using vim to edit yaml files).
The Problem: Configuration Drift and Manual Toil
While the initial manual setup worked, it was fragile. Every time I needed to recreate the environment or scale changes, I faced the same risks:
- Configuration Drift: Manually changing security group rules or Docker Compose files makes it impossible to know exactly what is running on the server without logging in.
- Secret Management: Handling
.envfiles and Cloudflare API tokens manually is a recipe for accidental leaks. Or just human error. - The "Wait, What Changed?" Experience: Without automation, verifying that my deployment was actually correct required tedious manual inspection.
The Solution: A Two-Tiered Automation Strategy
I decided to split the automation into two distinct layers: Infrastructure provisioning with Terraform and configuration management with Ansible.
Layer 1: Terraform & Cloud-Init (The Foundation)
Terraform now handles everything on the public cloud (AWS). When I run terraform apply, it provisions a complete, isolated network environment:
- A VPC with public subnets.
- An Internet Gateway and Route Tables.
It also manages the EC2 instance itself, including the Elastic IP (to ensure my Cloudflare DNS records don't break when the instance stops) and the Security Group.
To bridge the gap between raw virtual machine and "ready for Docker," I use cloud-init. During the very first boot, the instance automatically installs Docker and Docker Compose. This means the moment Terraform says the instance is "running", the foundation is already being built without me ever touching an SSH terminal.
Layer 2: Ansible (The Orchestrator)
Once the hardware exists, Ansible takes over to configure the software stack. This is the part that blew me away and, genuinely, got me hooked. Instead of manually copying .env files and running docker compose up, I use Ansible playbooks to:
- Verify the Host: Ensure the EC2 instance meets all prerequisites.
- Secure Secret Management: I use Ansible Vault to encrypt my sensitive environment variables (like
WEBUI_SECRET_KEYandCF_DNS_API_TOKEN). The playbook decrypts these on-the-fly and places them securely on the remote host with restrictive permissions (0600). - Deploy the Stack: Ansible pulls the latest Docker Compose configuration and starts the services.
The beauty of this approach is the ability to run ansible-playbook setup.yaml a second time and see the message that nothing has changed - meaning my deployment is idempotent and exactly as defined in my repository.
Architectural Evolution: Traefik & Cloudflare DNS-01
With automation came the opportunity to improve security. In the original setup, I had to worry about exposing ports. Now, I use Traefik as a reverse proxy on the EC2 instance.
By leveraging the Cloudflare DNS-01 challenge, Traefik can obtain valid SSL certificates without needing to expose port 80 to the public internet. It communicates with Cloudflare via API to prove ownership of the domain, handles the certificate renewal, and terminates HTTPS at the edge. This significantly shrinks my attack surface; only port 443 (and my trusted SSH IP) needs to be open in the Security Group.
The Agentic Frontier: Infrastructure as Context
Beyond just human convenience, this shift to IaC opens a massive door for agentic workflows. Because this infrastructure is now expressed as structured, readable code, it provides the perfect context for LLMs and AI agents. When an AI agent has access to this repository, it's looking at a complete blueprint of my cloud and local environment. This makes it incredibly powerful for:
- Automated Documentation: Generating updated READMEs or architecture diagrams directly from the Terraform/Ansible state.
- Intelligent Troubleshooting: An agent can parse logs, compare them against the intended configuration in Ansible, and pinpoint exactly where a drift has occurred.
- Autonomous Scaling & Deployment: I can envision workflows where an LLM-driven agent monitors performance and autonomously executes
terraform applyoransible-playbookto scale resources or roll out new configurations based on natural language instructions or monitoring triggers.
The code is not only a way to manage my homelab, but also serves as the knowledge base for the next generation of automation.
Reflecting on the Workflow
Moving from manual configuration to Infrastructure as Code has changed how I interact with my homelab. It’s no longer a collection of "servers I configured"; it’s a documented, version-controlled system.
The manual approach is great for prototyping, but this automated workflow is what makes my homelab feel like professional infrastructure.
You can find the full automation implementation in my homelab repository.