Manage Cloud Resources via Code Pipelines

Learn to manage cloud resources via code pipelines in this CI/CD foundations tutorial. Step-by-step guidance on infrastructure as code, hands-on exercises, and best practices for DevOps.

Focus: manage cloud resources via code pipelines

Sponsored

Your application works locally, but deploying to the cloud is a manual ritual of console clicks, environment drift, and late-night rollbacks. The pain is real: every hand-configured server, database, or load balancer becomes a snowflake that nobody can reproduce. The fix is to manage cloud resources via code pipelines — turning infrastructure into versioned, reviewable code that pipelines provision, update, and destroy automatically. This is the foundation of modern DevOps, and in this lesson you’ll move from fragile click-ops to repeatable, auditable deployments.

The problem this lesson solves

Manual cloud management creates a cascade of problems that slow every team:

  • Configuration drift: A developer tweaks a security group in the console, and now production looks nothing like staging.
  • No audit trail: Who changed the database size? Why? The console doesn’t tell you.
  • Slow, error-prone rollouts: Every deployment is a fragile sequence of hand-typed commands, and one typo can take down the service.
  • Scaling is terrifying: Adding a new environment means repeating every manual step, hoping you didn’t miss something.

Pro tip: Even a single unmanaged EC2 instance is a snowflake. If a developer can ssh in and change something by hand, your environment is already drifting from your code.

These issues aren’t just annoying — they directly cause outages and slow feature delivery. Managing cloud resources via code pipelines replaces this chaos with a deterministic, collaborative workflow where infrastructure is code, and pipelines are the hands that apply it.

Core concept / mental model

Think of your cloud account as a blank canvas. Infrastructure as Code (IaC) is the painting, and your pipeline is the artist’s hand that paints it in a predictable, repeatable way. Instead of pointing and clicking in the cloud console, you write declarative or procedural code that describes your resources — databases, networks, compute, IAM roles — and then you run that code through a pipeline that creates or updates the real cloud resources.

There are two main paradigms for IaC:

  • Declarative: You describe the final state (e.g., “three EC2 instances with this AMI”), and the tool figures out how to get there. Examples: Terraform, AWS CloudFormation, Pulumi (declarative portions).
  • Procedural: You write step-by-step commands that create resources in order. Examples: AWS CDK, Pulumi (imperative user of libraries), Ansible.

Pipelines add the delivery mechanics: you push code to a repo, the pipeline lints, validates, plans, applies, and verifies the infrastructure. This brings: version control, peer review, automated testing, and a clear audit log for every change.

How it works step by step

Here’s the high-level flow of a typical cloud-infrastructure pipeline.

  1. Define infrastructure as code in files committed to a Git repository.
  2. Open a pull request — teammates review the changes, looking for security risks and misconfigurations.
  3. The pipeline runs in the background: initialize, validate, and plan the infrastructure change (without applying anything yet).
  4. Apply the changes to the target environment (dev, staging, production) after approval gates.
  5. Verify the deployment — smoke tests, health checks, or dependency checks confirm the resources work as expected.
  6. Monitor and iterate — every subsequent change goes through the same pipeline, keeping everything reproducible.

The key insight: the pipeline enforces a consistent path to production. Whether it’s a one-line fix or a major refactor, every change flows through the same rules.

Hands-on walkthrough

Let’s see this in action with a minimal example using HashiCorp Terraform and GitHub Actions. Your goal: deploy a basic AWS S3 bucket with a pipeline that validates, plans, and applies only after a manual approval.

1. Write the infrastructure code

Create main.tf:

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "app_data" {
  bucket = "my-app-data-$(var.environment)-$(var.random_suffix)"
  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

Add variables.tf for the environment and a random suffix to keep bucket names unique.

2. Create the pipeline definition

Create .github/workflows/deploy.yml (GitHub Actions):

name: Deploy Infrastructure

on:
  push:
    branches: [ main ]
    paths: [ 'infra/**' ]
  pull_request:
    paths: [ 'infra/**' ]

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.5.0

      - name: Terraform Init
        run: terraform init
        working-directory: infra

      - name: Terraform Validate
        run: terraform validate
        working-directory: infra

      - name: Terraform Plan
        if: github.event_name == 'pull_request'
        run: terraform plan -out=tfplan
        working-directory: infra

      - name: Terraform Apply
        if: github.event_name == 'push' && github.ref == 'refs/heads/main'
        run: terraform apply --auto-approve tfplan
        working-directory: infra
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

3. Run the pipeline and verify

Push the code to your repository. The pipeline will run plan on PRs and apply on merge to main. Check the output:

Terraform will perform the following actions:

  # aws_s3_bucket.app_data will be created
  + resource "aws_s3_bucket" "app_data" {
      + bucket = "my-app-data-dev-xyz123"
      + tags   = {
          + Environment = "dev"
          + ManagedBy   = "terraform"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Now any change to that bucket goes through the same pipeline — no more console fiddling.

Compare options / when to choose what

Choosing the right IaC tool for your pipeline depends on your cloud provider, team skills, and needs:

Tool Type Best for Pros Cons
Terraform Declarative Multi-cloud and large teams Cloud-agnostic, state management, rich ecosystem State file handling complexity
CloudFormation Declarative (native) AWS-only teams Deep AWS integration, no extra tooling Locked to AWS
AWS CDK Procedural (uses TypeScript/Python) Developers who want to code in real languages Reusable constructs, type safety Requires app code compilation
Pulumi Procedural (many languages) Teams already in Python/Go/TS Real programming languages, multi-cloud Smaller community than Terraform
Ansible Procedural (agentless) Configuration management + infrastructure Simple YAML, push-based Less cloud-native state tracking

When to choose each: Choose Terraform for provider-agnostic setups. Choose CloudFormation if you’re 100% AWS and want built-in rollback. Choose CDK or Pulumi if your team is code-first and wants to abstract logic. Ansible fits when you need to also manage software configuration, not just cloud resources.

Pro tip: If you’re just starting, Terraform is the de facto standard. Its plan/apply model is intuitive, and you can almost always find examples for the resource you need.

Troubleshooting & edge cases

Working with cloud resources in pipelines surfaces classic issues. Here’s how to fix them:

  • Pipeline fails with Error: Error acquiring the state lock — another process is running terraform apply. Fix: ensure your pipeline doesn’t run concurrent jobs on the same state; use -lock=true for plan/apply and add a concurrency guard in your workflow.
  • No valid credential sources found — your cloud credentials aren’t set in the pipeline environment. Fix: add them as secrets in your CI system and reference them in the workflow, or use OIDC (OpenID Connect) for short-lived tokens.
  • Resource already exists (e.g., S3 bucket name taken) — bucket names are globally unique. Fix: append a random suffix to your bucket name, as shown in the example.
  • Plan says it will destroy unexpected resources — your state file is out of sync with reality. Fix: run terraform plan locally to inspect, then terraform import any pre-existing resources or update your code to match real world.
  • Apply succeeds but resource isn’t healthy — the resource was created, but your app isn’t connecting. Fix: add health checks as a pipeline step (e.g., curl endpoint) after apply; also verify security groups and IAM roles.

What you learned & what's next

You now understand how to manage cloud resources via code pipelines. You can explain the core idea behind IaC and pipelines, and you completed a practical exercise deploying an S3 bucket via Terraform and GitHub Actions. You’ve solved the snowflake-server problem and made your infrastructure reproducible and auditable.

Next up in the CI/CD foundations path: connecting your existing code pipeline to the infrastructure pipeline for a complete end-to-end deployment. You’ll build on this foundation to scan for security misconfigurations and add more sophisticated approval gates.

Keep your infrastructure in code, and let the pipeline be your only hand on the wheel.

Practice recap

Try extending your pipeline: add a test job that runs terraform fmt --check and a manual-approval step before apply. Then modify the bucket tagging and see the pipeline plan the change in a pull request. This mirrors how real teams ship infrastructure changes.

Common mistakes

  • Storing cloud credentials directly in the pipeline YAML. Always use secret variables or OIDC.
  • Running terraform apply on pull requests instead of only on merge to main — leads to duplicate or unwanted changes.
  • Ignoring the Terraform state file — losing it means losing track of your infrastructure; use remote state with locking.
  • Not adding a random suffix to globally unique resource names like S3 buckets, causing failures when names collide.

Variations

  1. Using AWS CloudFormation with a deployment pipeline (CodePipeline) for pure AWS environments.
  2. Using Pulumi with Python or TypeScript to write infrastructure as real code with loops and conditionals.
  3. Using CDK to define stacks and deploy with a CI system that runs cdk synth and cdk deploy.

Real-world use cases

  • Deploy a multi-environment Kubernetes cluster: production pipeline applies changes only after staging tests pass.
  • Provision a data analytics stack (S3 buckets, Glue jobs, Redshift) from a code review-approved PR.
  • Automate rollback of a broken production database by reverting the IaC commit and letting the pipeline apply the old state.

Key takeaways

  • Infrastructure as code turns cloud setup into versionable, reviewable artifacts.
  • Pipelines enforce a consistent path to apply that code — plan, review, approve, apply, verify.
  • Declarative tools like Terraform keep state and show diffs, reducing drift.
  • Always use secrets or OIDC for cloud credentials in CI, never hard-code.
  • Separation of plan (read-only) and apply (write) steps gives teams control over changes.
  • Health checks after apply are essential to catch issues your code didn’t predict.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.