DevSecOps CI/CD Pipeline with Terraform

Build a CI/CD pipeline that validates and security-scans Terraform IaC on every push.

Introduction

30 Second Summary

One misconfigured server can expose sensitive data to the entire internet. In the real world, DevOps teams prevent this by automating security checks that catch problems before code ever reaches production.

In this project, you will build a DevSecOps CI/CD pipeline that automatically validates and security-scans your infrastructure code every time you push to GitHub.

What You'll Build

A complete DevSecOps pipeline using Terraform for infrastructure as code, GitHub Actions for CI/CD automation, and Trivy for security scanning.

By the end of this project, you'll have:

  • Terraform configuration files that define Docker infrastructure (provider, image, and container) as code.
  • A GitHub Actions workflow that runs format checks, validation, security scanning, and planning on every push.
  • A public GitHub repository with a green CI/CD pipeline visible in the Actions tab, ready to share with potential employers.
  • Secret Mission: Add Terraform variables and outputs to make your infrastructure configurable across environments.

Are there any prerequisites?

You'll need a GitHub account, Docker installed via Docker Desktop with WSL2 integration, a WSL Linux terminal, and basic comfort using a terminal. No cloud account or credit card is required.

Before We Start

Before writing any infrastructure code, you need the right tools in place. This step gets your environment ready so Terraform can manage Docker containers as infrastructure.

You will install the Terraform CLI, confirm Docker is reachable from your terminal, and create the project directory where all your code will live.

In this step, get ready to:
  • Install the Terraform CLI inside WSL.
  • Verify Docker is accessible from your WSL terminal.
  • Create a project directory with Git initialized and a Terraform-specific .gitignore file.
Install the Terraform CLI

Terraform is the tool you will use to define infrastructure as code. You will install it from HashiCorp's official package repository inside your WSL terminal.

  • Open your WSL terminal by pressing the Windows key, typing Ubuntu (or your installed Linux distribution), and pressing Enter.
  • Add the HashiCorp GPG key and repository, then install Terraform by running these commands:
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

What do these commands do?

  • The first line downloads HashiCorp's GPG key so your system can verify their packages are authentic.
  • The second line adds HashiCorp's package repository to your system's list of software sources.
  • The third line updates your package list and installs the terraform binary.

Expect password and confirmation prompts

The sudo commands will ask for your WSL user password. When apt install asks "Do you want to continue? [Y/n]", type Y and press Enter.

  • Confirm Terraform installed successfully by running:
terraform version

You should see output starting with Terraform v followed by a version number (such as 1.15.6 or newer).

✔️ I see a version number

Terraform is installed and ready. Move on to the next substep.

ⓧ Command not found

The install may not have completed or the shell did not pick up the new binary.

  • Run sudo apt update && sudo apt install terraform again and watch for errors in the output.
  • Close and reopen your WSL terminal, then run terraform version again.
  • If you still see an error, confirm the repository was added by running cat /etc/apt/sources.list.d/hashicorp.list and checking it contains a HashiCorp URL.
Verify Docker is accessible from WSL

Docker Desktop with WSL2 integration shares the Docker daemon with your Linux terminal. This means you can manage containers directly from WSL without extra configuration.

  • Make sure Docker Desktop is running on your Windows machine.
  • Test the connection by running this command in your WSL terminal:
docker ps

You should see a table with column headers (like CONTAINER ID and IMAGE). The table may be empty if no containers are running.

✔️ I see a table or empty list

Docker is working from WSL. You are ready to continue.

ⓧ Permission denied or connection error

Docker Desktop's WSL2 integration may not be enabled for your distribution.

  • Open Docker Desktop on Windows.
  • Go to Settings, then Resources, then WSL integration.
  • Toggle on your Linux distribution (e.g. Ubuntu) and click Apply & restart.
  • Close and reopen your WSL terminal, then run docker ps again.
Create your project directory

Every Infrastructure as Code project lives in its own directory with version control. You will create the folder, initialize a Git repository, and add a .gitignore to keep Terraform's generated files out of version control.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, move into it, and initialize a Git repository by running these commands:
mkdir devsecops-pipeline
cd devsecops-pipeline
git init

What does git init do?

Git is a version control system that tracks every change you make to your code. Running git init creates a hidden .git folder inside your project that stores this history.

  • Confirm the directory and repository were created by running:
ls -a

You should see .git in the output, confirming Git is tracking this folder.

Don't see .git in the output?

  • Make sure you ran cd devsecops-pipeline before git init. Running pwd shows your current directory.
  • If you see ~/Desktop instead of ~/Desktop/devsecops-pipeline, run cd devsecops-pipeline and then git init again.

help me figure out why git init isn't working in my project directory

  • Open this project in VS Code by running:
code .

First time opening VS Code from WSL?

The first time you run code . from WSL, it may take a moment to install the VS Code Server component. A VS Code window will open connected to your WSL environment.

  • In VS Code, create a new file called .gitignore by clicking File, then New File.
  • When prompted for a file name, type .gitignore and press Enter.
  • Paste the following content into the file:
.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl

What does this .gitignore do?

  • .terraform/ excludes the directory where Terraform downloads provider plugins. These are large and can be re-downloaded with terraform init.
  • *.tfstate and *.tfstate.backup exclude state files that contain sensitive information about your infrastructure.
  • .terraform.lock.hcl excludes the dependency lock file. For this learning project, you do not need it in version control.
  • Save the file by pressing Ctrl+S.

✔️ Awesome, I've got everything!

Great. Double check you have saved .gitignore in the root of your devsecops-pipeline folder.

ⓧ I'd like to double check the full code

Here is the complete .gitignore file. Make sure yours matches exactly:

.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl

Your environment is ready. Next up, you will write Terraform configuration to define a Docker container as infrastructure.

Write Your Terraform Configuration

Your environment is set up and ready to go. Now it's time to define your infrastructure as code.

Terraform uses its own language called HCL (HashiCorp Configuration Language) to describe infrastructure. You'll write a configuration file that tells Terraform exactly which Docker resources to create.

In this step, get ready to:
  • Create a Terraform configuration file with the Docker provider.
  • Define resources for a Docker image and container.
  • Initialize Terraform and validate your configuration.
Create the provider configuration and image resource

Every Terraform project starts with a provider. Providers are plugins that let Terraform talk to specific platforms like AWS, Azure, or in your case, Docker.

  • Create a new file called main.tf inside your project directory by running:
touch main.tf
  • Confirm the file exists by running:
ls

You should see main.tf and .gitignore in the output.

  • Open main.tf in your code editor and add the following code:
# Declare the Docker provider and pin its version
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "4.4.0"
    }
  }
}

# Configure the provider with default settings
provider "docker" {}

# Pull the Nginx image from Docker Hub
resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = true
}

What does this code do?

  • The terraform block tells Terraform which provider plugin to download. Here you're pinning the kreuzwerker/docker provider at version 4.4.0.
  • The provider "docker" {} block configures the provider with default settings. On WSL2 with Docker Desktop integration, Terraform automatically finds the Docker daemon.
  • The docker_image resource pulls the nginx:latest image. Setting keep_locally = true prevents Terraform from deleting the image when you destroy the container later.
  • Save main.tf.

Not sure where to save?

Make sure you're saving main.tf in the same project directory where you ran git init in the previous step. If you're using VS Code, check the Explorer sidebar to confirm the file is at the top level of your project folder.

help me confirm main.tf is in the right directory

Add the container resource

You have a Docker image defined. Now you need a container resource that actually runs that image and exposes it on a port you can visit in your browser.

  • In main.tf, add the following code below the docker_image resource:
# Run a container from the Nginx image
resource "docker_container" "nginx" {
  name  = "devops-nginx"
  image = docker_image.nginx.image_id

  # Map host port 8080 to container port 80
  ports {
    internal = 80
    external = 8080
  }
}

What does this code do?

  • The docker_container resource creates a running container named devops-nginx.
  • The image argument references the image you defined above using docker_image.nginx.image_id. This is how Terraform links resources together.
  • The ports block maps port 8080 on your machine to port 80 inside the container. You'll visit localhost:8080 to see the running web server.
  • Save main.tf.

Seeing red squiggly lines in your editor?

If your editor shows syntax warnings, make sure you have matching opening and closing braces for every block. HCL uses curly braces {} around block bodies, not indentation.

help me fix HCL syntax errors in my main.tf

✔️ Awesome, I've got everything!

Great. Double check you've saved main.tf before moving on to the next substep.

ⓧ I'd like to double check the full code

Here's the complete main.tf file. Make sure yours matches exactly:

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "4.4.0"
    }
  }
}

provider "docker" {}

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = true
}

resource "docker_container" "nginx" {
  name  = "devops-nginx"
  image = docker_image.nginx.image_id

  ports {
    internal = 80
    external = 8080
  }
}
Initialize and validate your configuration

Before Terraform can manage any infrastructure, it needs to download the provider plugin you declared. This is what terraform init does.

  • Download the Docker provider plugin by running this command:
terraform init

You should see output that includes lines like:

Initializing provider plugins...
- Finding kreuzwerker/docker versions matching "4.4.0"...
- Installing kreuzwerker/docker v4.4.0...

What does terraform init do?

This command reads your required_providers block, downloads the matching plugin from the Terraform Registry, and stores it in a local .terraform/ directory. That's the directory your .gitignore already excludes.

You only need to run terraform init once per project (or again if you change providers).

Seeing a provider installation error?

Make sure you spelled the provider source exactly as kreuzwerker/docker with version 4.4.0 in your main.tf. Also confirm you have an internet connection, since Terraform downloads the plugin from the Terraform Registry.

help me fix my terraform init error

  • Verify your configuration is syntactically correct by running:
terraform validate

You should see this output:

Success! The configuration is valid.

Seeing a validation error?

If terraform validate reports an error, read the error message carefully. It usually points to the exact line and character where the problem is. Common issues include missing closing braces, typos in resource type names, or referencing a resource that doesn't exist.

help me understand my terraform validate error

Your Terraform configuration is valid and ready to deploy. In the next step, you'll run terraform apply to bring this infrastructure to life and see Nginx running in your browser.

Deploy and Destroy Infrastructure Locally

Your Terraform configuration is written and validated. Now it's time to see infrastructure as code in action by running the full Terraform lifecycle.

In this step, you'll execute the three core Terraform commands that every DevOps engineer uses daily: plan, apply, and destroy. This workflow lets you preview changes before they happen, create real infrastructure, and cleanly tear it all down.

In this step, get ready to:
  • Preview infrastructure changes with terraform plan.
  • Create a running Nginx container with terraform apply.
  • Tear down all infrastructure with terraform destroy.
Preview and apply your infrastructure

Before Terraform creates anything, it shows you exactly what it plans to do. This preview step is critical in production environments where an unexpected change could take down a service.

  • Preview what Terraform will create by running this command:
terraform plan

You should see output showing 2 resources to add: a docker_image and a docker_container. Resources marked with a + symbol mean Terraform will create them.

What does terraform plan show?

The plan command compares your configuration to the current state and proposes a set of changes. It reads what exists (nothing yet) and compares it to what you declared in main.tf.

The + symbol means "create". You'll also see the specific attributes each resource will have, like the container name and port mapping. No changes are actually made until you run apply.

  • Create the infrastructure by running this command:
terraform apply

What happens during apply?

Terraform will show the same plan you just previewed, then ask for confirmation. It displays the prompt: "Do you want to perform these actions?" and only accepts yes to proceed.

  • Type yes when Terraform asks "Do you want to perform these actions?" and press Enter.

Terraform pulls the Nginx Docker image and starts the container. You should see output ending with "Apply complete! Resources: 2 added, 0 changed, 0 destroyed."

Seeing an error about the Docker daemon?

Make sure Docker Desktop is running and WSL2 integration is enabled. You can verify by running docker ps in your WSL terminal. If that command fails, open Docker Desktop and check that it is fully started.

If port 8080 is already in use, you'll see a bind error. Stop whatever is using that port, then run terraform apply again.

help me fix a Docker daemon or port conflict error when running terraform apply

Verify your running infrastructure

Terraform reported success, but the real test is whether the infrastructure actually works. Let's confirm the Nginx web server is serving traffic.

  • Open your browser and navigate to http://localhost:8080.
  • You should see the Nginx welcome page with the heading "Welcome to nginx!" confirming the container is running and serving traffic.
  • Confirm Terraform is managing the container by running this command:
docker ps

You should see a container named devops-nginx in the list, with port 0.0.0.0:8080->80/tcp in the PORTS column.

Why use docker ps after apply?

Running docker ps confirms that Terraform actually created a real Docker container, not just updated its own state file. This is the "trust but verify" approach that DevOps teams use after every deployment.

Destroy your infrastructure

In real DevOps work, you tear down environments just as often as you create them. Terraform tracks every resource it creates, so it can cleanly remove everything in one command.

  • Tear down all Terraform-managed infrastructure by running this command:
terraform destroy
  • Type yes when Terraform asks "Do you really want to destroy all resources?" and press Enter.

Terraform removes the container and the image reference. You should see output ending with "Destroy complete! Resources: 2 destroyed."

  • Verify the container is gone by running:
docker ps

The devops-nginx container should no longer appear in the list. If you refresh http://localhost:8080 in your browser, it should fail to connect.

What is Terraform state?

Terraform keeps a terraform.tfstate file that maps your configuration to real-world resources. When you ran terraform destroy, Terraform used this file to know exactly which container and image to remove.

Your .gitignore already excludes state files from version control. State files can contain sensitive data and should never be committed to a public repository.

Container still showing in docker ps?

If the container still appears, make sure you typed yes (not y or Y) at the confirmation prompt. Terraform only accepts the full word yes.

help me troubleshoot why terraform destroy did not remove my container

You've now executed the complete Terraform lifecycle: plan, apply, and destroy. Next up, you'll automate the validation side of this workflow using GitHub Actions so that every code push is automatically checked before it can reach production.

Create a GitHub Actions CI/CD Pipeline

You've proven that Terraform can create and destroy infrastructure on your machine. But in a real team, multiple people push code to the same repository. Without automation, broken Terraform code can slip through unnoticed.

In this step, you'll build a GitHub Actions CI/CD pipeline that automatically validates your Terraform code every time you push. This catches formatting mistakes, syntax errors, and invalid configurations before they reach production.

In this step, get ready to:
  • Create a GitHub Actions workflow file that triggers on every push.
  • Define CI/CD steps that format-check, initialize, and validate your Terraform code.
  • Push your project to GitHub and watch the pipeline run.
Create the workflow directory

GitHub Actions looks for workflow files inside a specific directory structure: .github/workflows/. Any YAML file in that folder becomes a workflow that GitHub can run.

  • Create the workflow directory by running this command:
mkdir -p .github/workflows
  • Confirm the directory was created by running:
ls -la .github/workflows

You should see an empty directory listed. This is where your workflow file will live.

Seeing "No such file or directory"?

Make sure you're in your project's root directory (the folder containing main.tf). Run ls to check that you can see main.tf and .gitignore in the output.

Help me find my project directory so I can create the workflow folder.

Write the CI/CD workflow file

The workflow file defines what GitHub should do when you push code. You'll set up a job that checks your Terraform formatting, initializes the provider configuration, and validates the syntax.

  • Create a new file called terraform-ci.yml inside your .github/workflows/ directory.
  • Open terraform-ci.yml in your code editor and paste the following workflow configuration:
name: "Terraform DevSecOps Pipeline"

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  terraform:
    name: "Terraform Validate and Scan"
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v4

      - name: Terraform Format Check
        run: terraform fmt -check

      - name: Terraform Init
        run: terraform init -backend=false

      - name: Terraform Validate
        run: terraform validate

What does this workflow do?

  • on: push and pull_request trigger this workflow whenever code is pushed to main or a pull request is opened.
  • actions/checkout@v4 pulls your repository code onto the runner so the other steps can access your files.
  • hashicorp/setup-terraform@v4 installs the Terraform CLI on the GitHub Actions runner.
  • terraform fmt -check fails the pipeline if any file has inconsistent formatting.
  • terraform init -backend=false downloads the provider plugins without configuring a remote state backend. This is ideal for a validation-only pipeline.
  • terraform validate checks that your configuration is syntactically valid and internally consistent.

Why -backend=false?

In real DevSecOps, terraform apply runs in controlled environments with proper credentials. The CI pipeline's job is to gate bad code before it reaches those environments. Skipping the backend means your pipeline can validate code structure without needing cloud credentials or state storage.

  • Save terraform-ci.yml.

YAML indentation errors?

YAML is sensitive to spacing. Every indentation level must use exactly 2 spaces (not tabs). If your editor uses tabs by default, check the bottom status bar for a "Spaces: 2" setting.

Help me fix YAML indentation in my workflow file.

✔️ Awesome, I've got everything!

Great. Double check you've saved the file before moving on.

ⓧ I'd like to double check the full code

name: "Terraform DevSecOps Pipeline"

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  terraform:
    name: "Terraform Validate and Scan"
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v4

      - name: Terraform Format Check
        run: terraform fmt -check

      - name: Terraform Init
        run: terraform init -backend=false

      - name: Terraform Validate
        run: terraform validate
Push your project to GitHub

Your pipeline is defined, but it only runs when code is pushed to GitHub. You need to create a repository on GitHub, connect it to your local project, and push your code.

  • Open your browser and go to github.com.
  • Click the + icon in the top right corner, then select New repository.
  • Enter a repository name (for example, terraform-devsecops-pipeline).
  • Select Public for visibility.
  • Leave all checkboxes unchecked (do not add a README, .gitignore, or license).
  • Click Create repository.

Why leave the repository empty?

Initializing a GitHub repo with a README or .gitignore creates a commit on the remote that your local branch doesn't have. This causes merge conflicts when you try to push. Starting empty keeps the push clean.

GitHub shows a "Quick setup" page with your repository URL. You'll use this URL in the next commands.

  • Copy the HTTPS URL from the Quick setup page (it looks like https://github.com/USERNAME/REPO.git).
  • Back in your WSL terminal, stage and commit all your project files by running:
git add .
git commit -m "Add Terraform config and CI/CD pipeline"
  • Connect your local repository to GitHub and push by running:
git remote add origin [[REPO_URL="https://github.com/YOUR_USERNAME/your-repo-name.git"]]
git push -u origin main

What do these commands do?

  • git remote add origin links your local repository to the GitHub repository you just created.
  • git push -u origin main uploads all your committed files to GitHub and sets main as the default branch to push to in the future.

GitHub may prompt you to authenticate. Follow the prompts to sign in with your GitHub credentials.

  • In your browser, navigate to your repository on GitHub.
  • Click the Actions tab at the top of the page.

You should see your workflow running (or already completed). A green checkmark next to each step means your Terraform code passed all validation checks.

Pipeline not showing up in the Actions tab?

Make sure your workflow file is at the exact path .github/workflows/terraform-ci.yml. The .github folder must start with a dot, and workflows must be plural. Check by running:

ls .github/workflows/terraform-ci.yml

If the file isn't found, recreate the directory with the correct path and push again.

Help me debug why my GitHub Actions workflow isn't running.

Your pipeline is live. Every time you push code, GitHub automatically validates your Terraform configuration. Next up, you'll add security scanning to catch misconfigurations before they reach production.

Add Trivy Security Scanning

Your pipeline already catches formatting errors and invalid configurations before they reach production. But there's a gap: it doesn't check whether your infrastructure code follows security best practices.

In this step, you'll add Trivy to your pipeline. Trivy scans your Terraform files for security misconfigurations and blocks the pipeline when it finds critical issues.

In this step, get ready to:
  • Add a Trivy security scanning step to your GitHub Actions workflow.
  • Configure Trivy to fail the pipeline on critical and high severity findings.
  • Push changes and review the scan results in the Actions log.
Add the Trivy scan step to your workflow

Trivy is an open-source security scanner that checks infrastructure-as-code files against a database of known misconfigurations. You'll add it as a step in your workflow that runs before the Terraform commands.

  • In your code editor, open .github/workflows/terraform-ci.yml.
  • Find the Checkout code step. Add the following new step directly below it, before the Setup Terraform step:
      - name: Run Trivy IaC security scan
        uses: aquasecurity/trivy-action@v0.36.0
        with:
          scan-type: 'config'
          format: 'table'
          exit-code: '1'
          severity: 'CRITICAL,HIGH'

What does this step do?

  • uses: aquasecurity/trivy-action@v0.36.0 runs the Trivy scanner as a GitHub Action. The version is pinned to v0.36.0 for reproducible builds.
  • scan-type: 'config' tells Trivy to scan for IaC misconfigurations specifically (Terraform, Dockerfiles, Kubernetes manifests).
  • format: 'table' outputs the results as a readable table in the Actions log so you can see exactly what was flagged.
  • exit-code: '1' makes the step fail (exit with code 1) when findings match your severity threshold. This blocks the pipeline.
  • severity: 'CRITICAL,HIGH' only flags findings at these two severity levels. Lower-severity findings (MEDIUM, LOW) are reported but won't block the pipeline.

Why scan before Terraform commands?

Trivy runs before terraform init and terraform validate because it only needs the raw .tf files to check for misconfigurations. This means security issues are caught at the earliest possible point in the pipeline.

  • Save terraform-ci.yml.

YAML not looking right?

YAML is sensitive to indentation. Make sure the - name: line for the Trivy step is aligned with the other - name: lines in the steps list (6 spaces of indentation).

Also confirm the Trivy step sits between Checkout code and Setup Terraform. If it's in the wrong position, the scan may not have the files it needs.

help me fix the YAML indentation in my Trivy workflow step

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

name: "Terraform DevSecOps Pipeline"

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  terraform:
    name: "Terraform Validate and Scan"
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run Trivy IaC security scan
        uses: aquasecurity/trivy-action@v0.36.0
        with:
          scan-type: 'config'
          format: 'table'
          exit-code: '1'
          severity: 'CRITICAL,HIGH'

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v4

      - name: Terraform Format Check
        run: terraform fmt -check

      - name: Terraform Init
        run: terraform init -backend=false

      - name: Terraform Validate
        run: terraform validate
Push and review the Trivy scan results

With the Trivy step in place, push your changes to trigger the updated pipeline.

  • Commit and push your changes by running these commands in your terminal:
git add .
git commit -m "Add Trivy security scanning to pipeline"
git push
  • Switch back to your GitHub repository in the browser.
  • Click the Actions tab.
  • Click the most recent workflow run (it should show the commit message "Add Trivy security scanning to pipeline").
  • Click the Terraform Validate and Scan job to see the step-by-step log.
  • Expand the Run Trivy IaC security scan step to see Trivy's output.

You should see Trivy's scan output listing your main.tf file with a breakdown of tests run and any misconfigurations found.

What will I see in the Trivy output?

Trivy's output shows detected config files, test counts, and any failures grouped by severity. For each finding, Trivy names the misconfiguration and points to the relevant code in your .tf file.

Your pipeline may pass or fail depending on whether Trivy finds CRITICAL or HIGH issues in your Docker-based Terraform config. Both outcomes are valid. If it fails, you've just seen DevSecOps in action: the pipeline blocked code that doesn't meet security standards.

Pipeline failed on the Trivy step?

This is expected behavior, not an error. Trivy found CRITICAL or HIGH severity misconfigurations in your Terraform code. Expand the Trivy step log to read what it found. Common findings for Docker containers include running as root or missing health checks.

The pipeline failure demonstrates exactly what DevSecOps is about: catching security issues before deployment. You do not need to fix the findings for this project.

help me understand the Trivy findings in my pipeline

Secret mission

Make Your Infrastructure Configurable

Right now your Terraform configuration has hardcoded values for the container name and port. Real DevOps teams need infrastructure that adapts to different environments like dev, staging, and production. In this secret mission, you will introduce Terraform variables and outputs to make your infrastructure flexible and reusable.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally using Docker and a free public GitHub repository. There are no ongoing costs.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker container (created by terraform apply).
  • Project directory with Terraform configuration files.
  • Public GitHub repository with CI/CD pipeline.
  • Terraform CLI (installed via apt in WSL).

Keep everything running

No action needed. Choose this if you want to keep this project as a portfolio piece or continue experimenting.

  • Your GitHub repository stays public as a demonstration of DevSecOps practices. No costs are incurred.
  • The CI/CD pipeline only runs when you push code. It does not consume resources while idle.
  • If you have a Docker container running from terraform apply, it uses minimal local resources.
  • Terraform CLI remains installed in WSL for future projects.

Pause - I'll come back to this later

Stop running processes to free up local resources while keeping all your files intact.

  • If you have a running container from terraform apply, destroy it by running this command from your project directory:
  • Your GitHub repository and pipeline remain untouched and ready when you come back.
terraform destroy

Type yes when prompted to confirm.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Destroy any running Docker container by running this command from your project directory:
terraform destroy

Type yes when prompted to confirm.

  • Delete your local project directory by running:
cd ~/Desktop
rm -rf <your-project-folder-name>
  • Delete your GitHub repository:
  • Navigate to your repository on GitHub.
  • Click Settings (under the repository name).
  • Scroll down to the Danger Zone section.
  • Click Delete this repository.
  • Follow the confirmation prompts to verify the deletion.
  • Uninstall Terraform from WSL by running:
sudo apt remove terraform

Nice Work!

Nice Work!

Nice work! You just built a complete DevSecOps pipeline that automatically validates and security-scans your infrastructure code on every push to GitHub.

Here's what you learned:

  • Wrote Terraform HCL configuration to manage Docker containers as infrastructure, and executed the full Terraform workflow: init, plan, apply, and destroy.
  • Created a GitHub Actions CI/CD pipeline that automatically validates your infrastructure code on every push.
  • Integrated Trivy security scanning to catch IaC misconfigurations before deployment.
  • Secret Mission: Made infrastructure configurable with variables and outputs for multi-environment use.

Ready to quiz yourself?