All posts
DevOps 8 min read 0

A pipeline that provisions its own infrastructure

Most CI/CD pipelines assume the server already exists. Mine decides whether it needs to build one first — VPC, EC2, IAM roles, DNS, and all — before it ever touches the app.

October 10, 2026#Infrastructure as Code#Backend Development
A pipeline that provisions its own infrastructure

The pipeline most people picture when they hear "CI/CD" starts after the infrastructure already exists: a server is sitting there, waiting for a new image. I wanted the pipeline to own the layer before that too, so it starts with a one-time bootstrap stage — pushing every environment variable into SSM Parameter Store as KMS-encrypted values, and creating the S3 bucket that holds Terraform's state file. Everything after that is conditional. On every run, the pipeline checks whether the Terraform files changed at all; if nothing changed, the entire infrastructure job is skipped and it moves straight to the app. If something did change, Terraform provisions or updates the VPC, subnets, route tables, internet gateway, and EC2 instance, along with the IAM policies and roles the instance needs — including a GitHub OIDC role, so the pipeline can authenticate to AWS without a long-lived secret sitting in a repo. Once the instance exists, the pipeline updates DNS to point at its IP, since that address isn't guaranteed to stay the same across rebuilds. Only then does it get to what looks like a normal deploy: checking whether the nginx, web, or server folders changed, building the image and pushing it to GHCR, and using the OIDC role to send a deploy command directly to the instance — no persistent SSH key, no bastion host. That command pulls the env values back out of SSM and writes them to the file the app actually expects, so the encrypted parameters from the bootstrap stage end up doing something instead of just sitting in the store. From there it runs migrations and handles certbot's HTTPS renewal, all in the same execution. Splitting the pipeline this way — infrastructure as a conditional job, secrets as a fetch instead of a copy-pasted .env, and deploy as a signed command instead of an SSH session — was the part that actually changed how confidently I could tear the whole environment down and rebuild it from nothing.

Thanks for reading.

Back to all posts