Overview
Final project for the DevOps bootcamp. It sets up cloud infrastructure using Infrastructure as Code (IaC):
- Terraform — creates the AWS infrastructure
- Ansible — configures the servers
- Docker — runs the monitoring stack (Prometheus + Grafana) and the web server
Architecture
- AWS region:
ap-southeast-1 - VPC:
10.0.0.0/24with a public subnet (10.0.0.0/25) and a private subnet (10.0.0.128/25), plus one NAT gateway - 3 EC2 nodes (t3.small, Ubuntu 24.04, 16GB disk):
- node1 — private subnet (Prometheus + Grafana via Docker, Cloudflare Tunnel)
- node2 — private subnet (node_exporter)
- node3 — public subnet (nginx web server in Docker)
- Access: SSM (
EC2-SSM-Role) and SSH (keyyusof2-key)
Project Structure
.
├── .github/workflows/ # GitHub Actions CI/CD pipeline
├── terraform/ # AWS infrastructure (VPC, EC2, security groups, inventory)
├── ansible/ # Server setup (playbooks, templates, docker compose)
├── app/ # Web app (front-end "ship")
└── opencode.json # opencode config (terraform MCP)
Terraform — Create the Infrastructure
| File | Purpose |
|---|---|
providers.tf | AWS provider, S3 backend |
network.tf | VPC, public/private subnets, NAT gateway |
security.tf | Security groups (HTTP 80, SSH 22, exporters, Prometheus, Grafana) |
ec2.tf | 3 EC2 instances (ubuntu AMI, SSM role, user_data) |
inventory.tf | Generates inventory.ini for Ansible |
userdata.sh | Installs Docker + adds user to docker group |
userdata-tunnel.sh | Installs Docker + runs Cloudflare Tunnel (node1) |
How to run
cd terraform
terraform init
terraform apply -auto-approve
Ansible — Configure the Servers
| Playbook | Host | Purpose |
|---|---|---|
playbook-web.yaml | node3 | Runs nginx in Docker |
playbook-stack.yaml | node1 | Runs Prometheus + Grafana via Docker Compose |
playbook-prometheus.yaml | node1 | Installs Prometheus |
playbook-exporter.yaml | nodes | Installs node_exporter |
How to run
cd ansible
ansible-playbook playbook-web.yaml
ansible-playbook playbook-stack.yaml
ansible-playbook playbook-exporter.yaml
Application (app/)
The ship web app is a front-end built with Vite. The index.html content is generated from index.html.j2 during web deployment.
Prerequisites
- Terraform 1.15+
- Ansible + galaxy roles:
geerlingguy.docker,prometheus.prometheus.prometheus,prometheus.prometheus.node_exporter - AWS CLI and AWS credentials
- SSH key
~/.ssh/id_ed25519
Deploy Flow
- Terraform:
terraform apply— creates the VPC, security groups, 3 EC2 instances, and generatesinventory.ini - Ansible: run the playbooks you need (web or monitoring)
- Access: node3 via public IP; node1/node2 via SSM
CI/CD (GitHub Actions)
A minimal commit pipeline in .github/workflows/commit-pipeline.yml runs on every push to main:
| Step | Command |
|---|---|
| Install dependencies | npm ci |
| Preflight test | npm test |
| Unit tests | npm run test:unit |
| Build | npm run build |
Notes
terraform/.terraform, state files, and environment variables are excluded in.gitignore- The private security group only allows Prometheus/Grafana access from your current public IP
Architecture Diagram
To update this diagram later, just replace img/architecture-diagram.png with your own .png or .jpg — keep the same filename, or edit the src below.
View the repository on GitHub