Skip to content

Deploying Infrastructure

Deploying infrastructure shares many patterns with deploying software - the same pipeline tools, similar rollback strategies, and comparable trigger mechanisms. But infrastructure adds unique concerns: state management, drift detection, and the fact that changes can destroy running resources. This page covers the full deployment lifecycle: how changes reach production, how projects are structured, and which platforms orchestrate the process.


Understanding software deployment strategies provides the foundation for infrastructure deployment:

StrategyHow it worksBest for
PushA deployment tool runs whenever code changes - triggered by a pipeline stage, repo poll, or manual actionMost common; general-purpose
PullInfrastructure code defines the software to install (e.g., an RPM package in a server configuration)Simple installations; limited for complex sequencing or rollback
GitOpsSoftware agents continuously pull declarative state from a Git repo and reconcile actual state against itKubernetes workloads; drift detection built in

GitOps operates on four principles (per the CNCF):

  1. The desired state must be declarative
  2. The desired state must be versioned and immutable
  3. Agents must automatically pull state declarations
  4. Agents must continuously reconcile actual state against desired state

Unlike push/pull, GitOps doesn’t just deploy on new code - it redeploys whenever live state drifts from the source, automatically correcting manual changes or recovering from failures.


Infrastructure deployment strategies focus on how infrastructure provisioning is orchestrated alongside software workload deployment:

Infrastructure is provisioned separately from its workloads - often by a dedicated instance management team. This is the most common approach but frequently leads to lower effectiveness in delivery quality and flow.

A descriptor file within the application deployment specifies the required infrastructure. When deployed, a service reads the descriptor and:

  • Creates application-specific infrastructure (e.g., a dedicated database)
  • Integrates the application with shared services (monitoring, container clusters)

Standardisation efforts like the Score specification are emerging to replace custom in-house descriptor formats.

A leading-edge approach that embeds infrastructure definitions directly in application code. Resources may be provisioned at deploy time, first run, or the moment the code first uses them.

ImplementationExample
Embedded low-level IaCApplication code calls IaaS platform APIs directly
Imported librariesCDK constructs, Nitric SDK
Code annotationsFramework reads annotations and provisions matching resources

Tools: Ampt, Darklang, Winglang, Nitric, AWS CDK (serverless developers writing infrastructure in the same language as application code).


ModelDescriptionTrade-off
Local workstationRun tools directly from a laptop with a local code copyUseful for personal testing; causes overwrites and conflicts in teams
Central servicePull code from a repository and apply from a shared serverDeployment history, governance enforcement, no “works on my machine”
Delivery pipelineDeployment is a pipeline build stage; the service runs tools on an agentSingle team ownership; centralised visibility
Deployment service / TACOSSeparate the pipeline (code progression) from the deployment serviceHCP Terraform, Pulumi Cloud, AWS Service Catalog, Spacelift, Atlantis
Infrastructure as DataA controller continuously reconciles definitions with live infrastructureCrossplane, AWS ACK, Config Connector; drift correction is automatic

Infrastructure as Data extends IaC by treating drift detection and resolution as a core, continuous feature rather than a one-time check.

How it works:

  1. A central service (typically a Kubernetes cluster) runs a control loop
  2. The loop continuously compares infrastructure definitions against actual resources
  3. Discrepancies are automatically corrected

Native tools:

ToolCloud
ACKAWS
Config ConnectorGCP
Azure Service OperatorAzure
CrossplaneMulti-cloud

Custom controllers can run standard Terraform/Pulumi from within Kubernetes: HCP Terraform Operator, Pulumi Kubernetes Operator, Tofu Controller.

Alternative: IaSQL - uses a PostgreSQL database instead of Kubernetes; infrastructure defined as SQL, synchronised with AWS.

Deployments require orchestration beyond just running terraform apply - retrieving dependencies, assembling secrets, managing authentication, running post-deploy tests.

The spaghetti code trap: Custom wrapper scripts (Bash, Python, Make) tend to grow larger than the infrastructure code itself.

Best practices:

PrincipleWhat to do
Split the lifecycleSeparate scripts for building, testing, and deploying - never one monolith
Separate tasksKeep integration points between tasks loosely coupled
Decouple deployment levelsMulti-stack orchestration scripts ≠ single-stack deployment scripts
Keep scripts genericNo hardcoded project configurations; reusable for any project using similar tools
Test your scriptsValidate with ShellCheck, Bats, or equivalent; apply single-responsibility principle

Standardisation tools: Terragrunt, Atmos, InfraBlocks, Terraspace - purpose-built frameworks that replace ad hoc wrapper scripts.


GitOps mirrors standard software development:

  1. Developer checks out code, creates a branch, develops locally
  2. Opens a pull request - triggers CI tests and a speculative plan showing proposed infrastructure changes
  3. Reviewers evaluate the plan and code
  1. Once the PR is approved and merged to main, the CD orchestrator automatically deploys

Terraform can detect and fix drift, but only when manually triggered. True GitOps requires scheduled reconciliation - CD platforms run Terraform on a regular cadence, ensuring active infrastructure continuously matches the main branch, not just during PRs.


The root module is the entry point for every deployment - the only place users inject variables and configure providers. How you structure root modules across environments determines how safely you can roll out changes.

The application module is the root module. Environments are separated by variable files (staging.tfvars, production.tfvars).

my-app/
├── main.tf
├── variables.tf
├── staging.tfvars
└── production.tfvars

Each environment gets its own folder and main.tf - a thin wrapper that calls a versioned application module.

environments/
├── staging/
│ └── main.tf # source = "registry.example.com/app" version = "1.2.0"
├── production/
│ └── main.tf # source = "registry.example.com/app" version = "1.1.4"
└── modules/
└── app/ # The reusable application module

Key advantages:

  • Pin different module versions per environment - stage changes safely before production
  • Parameters can be hardcoded directly in the module block (no complex .tfvars files)
  • Upgrading an environment requires a PR, creating an explicit audit trail

Repository organisation:

ScenarioRecommendation
Single team manages all environmentsOne shared repository
Multiple teams own different environmentsSeparate repositories

Terragrunt takes the environment-as-root concept further - no literal Terraform root module is needed:

environments/staging/terragrunt.hcl
terraform {
source = "tfr:///org/app/aws?version=1.2.0"
}
inputs = {
environment = "staging"
instance_type = "t3.medium"
}
FeatureDetail
scaffoldReads a registry module and auto-generates the boilerplate terragrunt.hcl with required inputs
Mirror commandsterragrunt plan, terragrunt apply - generates the root module behind the scenes
run-allExecute commands across all environment configurations simultaneously (terragrunt run-all plan)
Version pinningNo constraint ranges - exact versions only; manual updates create a clear audit trail

DriverExample
Input material changeNew code commit, updated dependency, changed configuration value
RollbackReverting to a previous known-good version
Drift correctionLive infrastructure no longer matches the defined code

Best practice: bundle as many dependencies as possible with the versioned build rather than reassembling them at deploy time. Trigger deployments as soon as it is safe after any input changes.

MethodUse case
Pipeline / deployment service UISelect specific build versions - useful for rollbacks and tester self-service
Developer portalUser-facing interface with authorisation and billing; deploys tagged builds (“latest dev”, “production”)
Ticketing systemGovernance workflows - manages approvals, auditing, and compliance; integrates with pipelines
Source code branchesPromote by merging into an environment branch; deployment triggered by hook or poll

Pushing a commit kicks off the pipeline automatically. Builds progress through stages - deploy to test, run automated checks, deploy to integration - via automatic triggers at each stage boundary.

TypeMechanism
ActivePipeline/portal/ticket system makes an API call to the deployment service, passing build version and configuration
PassiveService continuously monitors source repos, artifact packages, configuration registries, or dependent resources; deploys when a change is detected

Every mature CD platform provides: GitOps-based workflows, role-based access control, OIDC support, secret management, and speculative plan generation.

FeatureDetails
TACOSPlatforms bundling delivery + state management + private registries; transparent state backend with version history UI
Drift detectionContinuous reconciliation; alerts (Slack) vs. automatic correction
Multi-IaC supportHelm, Pulumi, Ansible alongside Terraform - avoid maintaining separate systems
Policy enforcementEnforce rules at the deployment level; industry standardised on OPA / Rego (even HashiCorp added OPA support in 2023)
Cost estimationBuilt-in (HCP Terraform) or via Infracost; limited to major clouds; estimates only

HashiCorp’s Business Source License forbids competitors from using newer Terraform versions. Commercial platforms have shifted to OpenTofu. Teams wanting the newest HashiCorp Terraform must use HCP Terraform or self-host.

PlatformTypeStrengthsLimitations
HCP TerraformManaged TACOSDeep CLI integration, built-in cost estimationTerraform-only; per-resource hourly pricing can inflate costs
Env0 / SpaceliftManaged TACOSOpenTofu sponsors; multi-framework (Terragrunt, Helm, Ansible, Pulumi)-
ScalrManaged TACOSPure Terraform/OpenTofu focus; CLI-driven workflows; easy HCP migrationNo Helm/Kubernetes support
Digger / TerrateamGitOps PlusPR-comment-driven; tight GitHub integrationNo state backend or registry out of the box
Harness / Octopus DeployEnterprise CDBroad platform - physical hardware, containers, legacy systemsNo built-in registries or state management
Atlantis / TerrakubeSelf-hosted OSSFree; legally allows newest HashiCorp TerraformAdministrative burden; must isolate securely