Azure Container Apps Terraform Stack
A Terraform-provisioned Azure Container Apps environment running backend, frontend, and an internal-only Redis service, wrapped in a CI/CD pipeline that builds, promotes, and deploys every release without manual deployment commands.
Solely designed and built this Terraform-provisioned Container Apps environment and its surrounding CI/CD pipeline
Automated service discovery between containers via Terraform-injected environment variables, eliminating manual connection-string configuration
Restricted ingress so only the frontend is externally reachable; backend and Redis are both internal-only, minimizing the environment's attack surface
Configured scale-to-zero on idle containers to keep cost tied to actual usage
Chose Service Principal authentication over interactive login specifically to support unattended CI/CD pipeline runs
Structured Terraform modules into reusable single-resource building blocks plus a scaffolded factory module for stamping out repeatable resource combinations
Built a CI pipeline that containerizes every component and publishes release-candidate images to Azure Container Registry
Designed a gated CD pipeline (automatic Dev deploys, manually-triggered QA promotion, manual Stable retag) before the same Terraform project provisions or updates the environment
// overview
A Terraform-provisioned Azure Container Apps environment running backend, frontend, and an internal-only Redis cache, wrapped in a CI/CD pipeline that builds, promotes, and deploys every release without manual deployment commands.
I designed and built this end-to-end on my own: the Terraform infrastructure, the per-container ingress and cost controls, and the CI/CD pipeline that takes a build from source through release-candidate, QA, and stable promotion to a live environment.
// problem
Deploying multiple apps into a single container environment by hand means configuring secrets, environment variables, per-app settings, and ingress rules separately for each one, and every one of those is a place a human error can creep in; tracking down which manual step caused a given issue wastes real engineering time.
The problem compounds when a single product is made up of multiple containers, each with its own configuration. Repeating that setup correctly, every time, without drift or mistakes, isn't something manual deployment can reliably guarantee.
// approach
Started by understanding how the application worked locally, working out what each component needed to run, then containerized every part of it.
Ran the containers on a local container engine first to catch issues before touching any cloud infrastructure.
Decided to build a Terraform project to deploy the containerized app on Azure Container Apps.
Used that Terraform project to provision every resource from scratch where it didn't already exist: a resource group, a virtual network with a dedicated subnet delegated to Microsoft.App/environments (required for Container Apps VNet integration), a Log Analytics Workspace (a hard requirement for any Container Apps environment), and the Container Apps Environment itself. Authenticated using a Service Principal rather than interactive az login, specifically because I knew this project would eventually need to run unattended inside a CD pipeline, not just from a developer's machine.
Structured the Terraform code around two kinds of modules: single-purpose modules for common resources I'd reuse elsewhere (modules/general for the resource group, modules/network for the VNet and subnets), and a separate combination module (modules/con-app) designed as a factory for stamping out a full container-app resource group as one repeatable unit. That factory module is scaffolded but not yet wired into the root module (the root currently provisions the three container apps directly), so it's a deliberate next step rather than a finished part of the design.
Restricted ingress so only the frontend is reachable from outside the environment; backend and Redis are both internal-only, accessible exclusively from within the Container Apps environment, with no direct external path to either.
Configured the container apps to scale to zero when idle, so cost tracks actual usage instead of paying for always-on compute.
Wired runtime configuration automatically instead of by hand: the backend's Redis connection string and the frontend's backend address are both injected by Terraform from the actual deployed resource names, and secrets are sourced from tfvars/CI, with secret keys transformed to valid Container Apps naming automatically.
Built a CI pipeline that containerizes every component of the app and pushes the resulting images to Azure Container Registry, tagged as a release candidate.
Built a CD pipeline where the Dev stage deploys the release-candidate image automatically, the QA stage requires someone with the right permissions to trigger deployment to the QA environment, and once QA passes, a manually-triggered stage retags the image from release-candidate to stable and pushes that tag to the registry.
Added a final stage that runs the same Terraform project with the latest stable image, provisioning a new environment if none exists or updating the existing one in place if it does, so there's a single deployment mechanism for both first deploy and every update after it.
// architecture
// outcome
Manual configuration of secrets, environment variables, per-app settings, and ingress rules across multiple containers was replaced by a single Terraform project that provisions every resource from scratch and wires services together automatically, removing the human error that came from re-deriving connection details by hand on every deployment.
The CI/CD pipeline built around it automated the full path from build to live environment: every component is containerized and published as a release candidate automatically, Dev deployments require no manual step, and the same Terraform run provisions or updates the environment once a build is promoted to stable, with QA and production promotion kept behind deliberate, permissioned gates rather than removed entirely.