Running the Full Agentic Deployment Pipeline: Scaffold to Live CloudFront
Source: Dev.to
Phase 4 — Agentic Infrastructure
Building a Live AWS Deployment Pipeline with Claude Code
Phases 1‑3 built the foundation: a verified environment, a project‑aware agent, and four reusable Skills. Phase 4 uses that foundation to run the complete deployment pipeline—from an empty Terraform directory to a live static website on AWS CloudFront. This post documents every step, command, and output.
Execution Steps
| # | Command | Type | Output |
|---|---|---|---|
| 1 | /scaffold-terraform | Skill | 4 Terraform files generated |
| 2 | terraform init | Manual | Providers downloaded |
| 3 | /tf-plan | Skill | Plan: 4 create, 0 destroy |
| 4 | /tf-apply | Skill | 4 AWS resources provisioned |
| 5 | /deploy | Skill | Site live on CloudFront |
Generated Terraform Files
Claude read template-spec.md and generated the complete Terraform configuration in the terraform/ directory.
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── providers.tfmain.tf (excerpt)
# S3 bucket — versioning enabled, public access blocked
resource "aws_s3_bucket" "site" {
bucket = var.bucket_name
tags = var.tags
}
resource "aws_s3_bucket_versioning" "site" {
bucket = aws_s3_bucket.site.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "site" {
bucket = aws_s3_bucket.site.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# CloudFront origin access control
resource "aws_cloudfront_origin_access_control" "oac" {
name = "${var.bucket_name}-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# CloudFront distribution
resource "aws_cloudfront_distribution" "site" {
enabled = true
default_root_object = "index.html"
price_class = "PriceClass_200" # Africa + Europe coverage
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "S3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
default_cache_behavior {
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
}
restrictions {
geo_restriction { restriction_type = "none" }
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Bucket policy — CloudFront access only
resource "aws_s3_bucket_policy" "site_policy" {
bucket = aws_s3_bucket.site.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "cloudfront.amazonaws.com" }
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.site.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.site.arn
}
}
}]
})
}providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "af-south-1"
}Initialising Terraform
cd terraform/
terraform initOutput (truncated):
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.x.x...
- Installed hashicorp/aws v5.x.x (signed by HashiCorp)
Terraform has been successfully initialized!Note:
terraform initis intentionally not automated inside a Skill because it downloads provider plugins and sets up the backend—decisions that merit manual confirmation.
Planning
The Skill ran terraform validate, then terraform plan -out=tfplan.binary, and scanned the output for destructions.
Plan Summary
| Change | Count | Resources |
|---|---|---|
| Create | 4 | aws_s3_bucket, aws_cloudfront_distribution, aws_cloudfront_origin_access_control, aws_s3_bucket_policy |
| Modify | 0 | — |
| Destroy | 0 | — |
Zero destructions were detected, so the plan was deemed safe and presented for review before proceeding.
Applying
terraform apply tfplan.binaryResult (excerpt):
aws_cloudfront_origin_access_control.oac: Creating...
aws_s3_bucket.site: Creating...
aws_s3_bucket.site: Creation complete
aws_s3_bucket_versioning.site: Creating...
aws_s3_bucket_public_access_block.site: Creating...
aws_cloudfront_origin_access_control.oac: Creation complete
aws_cloudfront_distribution.site: Creating...
aws_cloudfront_distribution.site: Still creating... [10m elapsed]
aws_cloudfront_distribution.site: Creation complete
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.CloudFront propagation note: The distribution takes 8‑12 minutes to propagate globally after the apply finishes. While propagating, the status shows
InProgress; it changes toDeployedwhen ready. The site is accessible only after the status isDeployed.
Deploying Site Content
The Skill read the Terraform outputs for the bucket name and distribution ID, then executed:
# Sync site files
aws s3 sync ./site s3:/// --delete
# Trigger CloudFront cache invalidation
aws cloudfront create-invalidation \
--distribution-id \
--paths '/*'Output (excerpt):
upload: site/index.html to s3:///index.html
upload: site/styles.css to s3:///styles.css
{
"Location": "...",
"Invalidation": {
"Id": "...",
"Status": "InProgress"
}
}The site was confirmed live at the CloudFront URL.
Verification Checklist
| Check | Result |
|---|---|
Terraform files generated in terraform/ | Passed |
terraform validate — no errors | Passed |
| Plan: 4 to create, 0 to destroy | Passed |
S3 bucket created in af-south-1 | Passed |
| CloudFront distribution status: Deployed | Passed |
| Site files served correctly | Passed |
Deployment Checks
| Step | Status |
|---|---|
| Synced via AWS S3 sync | Passed |
| CloudFront invalidation triggered | Passed |
| Site accessible via CloudFront URL in browser | Passed |
Deployment Summary
The deployment itself was the least stressful part of this project. That is because the three preceding phases did the real work:
Phase 1 – Verify the environment
No ambiguous tool errors during deployment.Phase 2 – Load project memory
The agent knew the architecture, region, and conventions without prompting.Phase 3 – Define the Skills
Each step followed the same procedure, with the same checks, as designed.
A well‑structured pipeline is not one that merely handles problems well; it is one that makes certain categories of problem impossible.
Live site: