Infrastructure as Code for platform services (monitoring, observability, etc.) using Terraform and Ansible.
This repository manages infrastructure for:
- Peekaping: Monitoring service
- Backups: Terraform backups from Backblaze to Google Drive.
- Infrastructure: Terraform with Hetzner Cloud
- Configuration: Ansible (references roles from WebKit)
- Deployment: Docker Compose on VMs
- State: Backblaze B2 remote backend
platform/
├── terraform/
│ ├── base/ # Base Terraform configuration
│ │ ├── providers.tf # Hetzner provider
│ │ ├── backend.tf # B2 remote state
│ │ ├── variables.tf # Global variables
│ │ ├── outputs.tf # Global outputs
│ │ └── main.tf # Module imports
│ └── services/
│ └── peekaping/ # Peekaping monitoring service
├── ansible/
│ ├── playbooks/
│ │ └── peekaping.yaml # Peekaping deployment
│ └── ansible.cfg # References WebKit roles
├── docker/
│ └── peekaping/
│ ├── docker-compose.yml
│ └── .env.example
# Copy example tfvars
cp terraform.tfvars.example terraform.tfvars
# Edit with your Hetzner token and other values
vim terraform.tfvars# Initialize with B2 backend
make init
# Or manually:
# cd terraform/base
# terraform init \
# -backend-config="access_key=${BACK_BLAZE_KEY_ID}" \
# -backend-config="secret_key=${BACK_BLAZE_APPLICATION_KEY}"This repository uses automated Terraform workflows for infrastructure changes:
-
Pull Request - Terraform plan runs automatically
- Shows what infrastructure changes will be made
- Posts plan output as PR comment
- Detects destructive changes (deletions) and warns
- Validates Terraform syntax and security (tfsec, Checkov)
- Saves plan artifact for exact apply on merge
-
Merge to Main - Terraform apply runs with approval gate
- Downloads saved plan from PR (ensures plan-apply match)
- Requires approval on
productionenvironment before applying - Backs up state before making changes
- Posts results back to merged PR and commit
- Only runs if Terraform files changed
The repository includes reusable workflow helpers:
-
.github/workflows/helper-plan.yaml- Reusable plan workflow- Runs terraform plan
- Posts formatted results to PR
- Uploads plan artifact
- Detects destructive changes
-
.github/workflows/helper-apply.yaml- Reusable apply workflow- Downloads plan artifact from PR
- Backs up state before apply
- Applies infrastructure changes
- Requires GitHub environment approval
Workflows use GitHub organization secrets (already configured):
ORG_HETZNER_TOKEN- Hetzner Cloud API tokenORG_BACK_BLAZE_KEY_ID- Backblaze B2 access key for state backendORG_BACK_BLAZE_APPLICATION_KEY- Backblaze B2 secret key
The production environment must be configured with:
- Go to Settings → Environments → New environment
- Name:
production - Add protection rules:
- ✅ Required reviewers (select yourself)
- ✅ Deployment branches:
mainonly - Optional: Wait timer (0-30 minutes)
# 1. Create a feature branch
git checkout -b feat/add-new-service
# 2. Make your Terraform changes
vim terraform/services/new-service/main.tf
# 3. Push and create PR
git push origin feat/add-new-service
gh pr create --title "Add new service"
# 4. Review plan output in PR comment
# 5. Get PR approval, then merge
# 6. Approve the deploy in GitHub Actions (production environment)
# 7. Changes applied automatically!# Plan deployment
make plan
# Apply (creates VM, volume, firewall)
make apply
# Get access details
make outputIf the automated workflow fails:
- Check the workflow logs: Actions tab → Failed workflow → View logs
- Manual apply via workflow_dispatch: Actions → Terraform Apply → Run workflow
- Local manual apply: Use
make applywith local credentials - State recovery: Download backup from workflow artifacts (retained 90 days)
This project references Ansible roles from the WebKit repository:
docker- Docker installationnginx- Nginx web server with SSLcertbot- Let's Encrypt SSL certificatesufw- Firewall configurationfail2ban- Intrusion prevention
See ansible/ansible.cfg for role path configuration.
This project adapts patterns from WebKit's Terraform modules:
- Hetzner server provisioning
- Firewall rules
- SSH key generation
- Cloud-init integration