feat: Add Terraform CI/CD automation with plan on PR and apply on merge#12
Merged
ainsleyclark merged 3 commits intomainfrom Nov 18, 2025
Merged
feat: Add Terraform CI/CD automation with plan on PR and apply on merge#12ainsleyclark merged 3 commits intomainfrom
ainsleyclark merged 3 commits intomainfrom
Conversation
This implements automated Terraform workflows using reusable GitHub Actions: **New Reusable Workflows:** - helper-plan.yaml: Runs terraform plan, posts to PR, uploads plan artifact - helper-apply.yaml: Applies saved plan with approval gate, backs up state **PR Workflow Updates:** - Added terraform-plan job using helper-plan workflow - Plan output posted as PR comment with change summary - Detects and warns about destructive changes (deletions) - Plan artifact saved for exact apply on merge **New Apply Workflow:** - terraform-apply.yaml: Runs on push to main - Downloads plan artifact from merged PR - Requires approval on 'production' environment - Backs up state before applying (retained 90 days) - Posts results to PR and commit **Safety Features:** - Added prevent_destroy lifecycle to uptime-kuma volume - State backups before every apply - Plan artifact validation (ensures plan-apply match) - Destructive change detection with warnings - Concurrency controls to prevent parallel applies **Configuration:** - Uses org secrets: ORG_HETZNER_TOKEN, ORG_BACK_BLAZE_KEY_ID, etc. - Secrets inherit automatically via 'secrets: inherit' - Updated terraform.tfvars.example with CI/CD notes - Added comprehensive README documentation **Documentation:** - New CI/CD Automation section in README - Workflow overview and usage instructions - Emergency procedures for manual operations - GitHub environment setup guide Requires GitHub environment 'production' with approval gates to be configured. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Lifecycle blocks cannot be used in module blocks, only resource blocks. Data protection is still provided by: - State backups before every apply (90 day retention) - Approval gates on production environment - Plan artifact validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Terraform Plan Results📋 Changes detected: Plan: 1 to add, 0 to change, 1 to destroy. 📄 View Full Planmodule.uptime_kuma.module.server.tls_private_key.this: Refreshing state... [id=4fd1b70a8627413ffd7622f1a7fc3494ca6cc3f0]
module.uptime_kuma.module.volume.hcloud_volume.this: Refreshing state... [id=103988283]
module.uptime_kuma.module.server.hcloud_ssh_key.this: Refreshing state... [id=103946138]
module.uptime_kuma.module.server.hcloud_firewall.this: Refreshing state... [id=10203178]
module.uptime_kuma.module.server.hcloud_server.this: Refreshing state... [id=113429231]
module.uptime_kuma.module.server.hcloud_firewall_attachment.this: Refreshing state... [id=10203178]
module.uptime_kuma.module.volume.hcloud_volume_attachment.this: Refreshing state... [id=103988283]
module.uptime_kuma.local_file.ansible_inventory: Refreshing state... [id=db62035f770af622c86541e9206a06e4a6a438ed]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# module.uptime_kuma.local_file.ansible_inventory must be replaced
-/+ resource "local_file" "ansible_inventory" {
~ content = <<-EOT # forces replacement
# Auto-generated by Terraform - DO NOT EDIT MANUALLY
- # Last updated: 2025-11-18T16:38:57Z
[uptime_kuma]
uptime.ainsley.dev ansible_host=46.224.59.113 ansible_user=root
[uptime_kuma:vars]
domain=uptime.ainsley.dev
- admin_email=hello@ainsley.dev
+ admin_email=admin@ainsley.dev
EOT
~ content_base64sha256 = "+LZyjjsMZNNgzpo+xx/UeNIxlCDr+Dci/bMafudXtNY=" -> (known after apply)
~ content_base64sha512 = "YJNqGPF5frlHBAKliavy059OJRamAh0R6WDlGUAcAhGuPpbdbGE9bM80XNnQMYx6Pv1nmeNQxvHtVv/MzuAz7g==" -> (known after apply)
~ content_md5 = "6b6791c5c2461e1cdd41052cf1c2e77d" -> (known after apply)
~ content_sha1 = "db62035f770af622c86541e9206a06e4a6a438ed" -> (known after apply)
~ content_sha256 = "f8b6728e3b0c64d360ce9a3ec71fd478d2319420ebf83722fdb31a7ee757b4d6" -> (known after apply)
~ content_sha512 = "60936a18f1797eb9470402a589abf2d39f4e2516a6021d11e960e519401c0211ae3e96dd6c613d6ccf345cd9d0318c7a3efd6799e350c6f1ed56ffcccee033ee" -> (known after apply)
~ id = "db62035f770af622c86541e9206a06e4a6a438ed" -> (known after apply)
# (3 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"📝 To apply these changes: Merge this PR. Terraform will apply automatically after approval on the |
…PR comments - Remove timestamp() from local_file.ansible_inventory to prevent constant replacements - Remove duplicate "Terraform Plan Summary" heading from PR comments - Clean up emoji from main PR heading for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🚀 Terraform Apply Results✅ Infrastructure changes applied successfully 📄 View Apply Output |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements full Terraform CI/CD automation using reusable GitHub Actions workflows:
Changes
New Workflows
.github/workflows/helper-plan.yaml- Reusable plan workflowterraform planwith org secrets.github/workflows/helper-apply.yaml- Reusable apply workflow.github/workflows/terraform-apply.yaml- Apply on merge to mainproductionenvironmentModified Files
.github/workflows/pr.yamlterraform-planjob using helper-plan workflowterraform/services/uptime-kuma/main.tfprevent_destroylifecycle to volume moduleterraform.tfvars.exampleREADME.mdHow It Works
On Pull Request
tfplan-pr-{number}On Merge to Main
productionenvironment ⏸️Setup Required
GitHub Environment Configuration
Before merging, create the
productionenvironment:productionmainonlyWithout this environment, the apply workflow will fail.
Testing Plan
This PR will test itself! When merged:
productionenvironmentBenefits
✅ Visibility: See infrastructure changes before merge
✅ Safety: Approval gates, state backups, prevent_destroy
✅ Consistency: Same process every time, no manual steps
✅ Documentation: Automatic changelog via PR comments
✅ DRY: Reusable workflows for future projects
✅ Secrets: Uses existing org secrets, no manual setup
Risk Mitigation
🤖 Generated with Claude Code