In the previous article, we saw how to set up Terraform to build our infrastructure using code. Now, we’ll learn how to use GitLab’s automation to deploy our changes. That means whenever we change something in our code, GitLab will automatically apply those changes—so we don’t have to do it by hand every time.
Setup Overview
Version Control with GitLab: Store your Terraform configurations in a GitLab repository. This approach takes advantage of GitLab’s version control capabilities to track and review changes in infrastructure code.
Automated Pipelines: Utilize GitLab CI/CD pipelines to automate the process of testing and deploying the Terraform configurations. Pipelines can be configured to trigger on commits, merging requests, or manual requests.
AWS Integration: Configure Terraform to manage AWS resources by using the AWS provider. The AWS credentials and access controls should be securely managed, often using environment variables or encrypted secrets in GitLab.
Typical Pipeline Steps
Initiate: Initialize Terraform configurations in the pipeline. This step sets up Terraform to manage the specified resources.
Validate: used to verify the correctness of Terraform configuration files within a directory. It performs a static analysis of the configuration to ensure it is syntactically valid and internally consistent.
Plan: Execute terraform plan to preview changes without applying them. This helps in reviewing potential impacts before changes go live.
Apply: Run terraform apply to apply the planned changes to your AWS environment. This step can be set to manual to require approval, ensuring changes are reviewed.
Destroy: The terraform destroy command is used to delete all infrastructure that Terraform has created and is currently managing. it removes all resources defined in your .tf files and tracked in the state file — EC2 instances, VPCs, S3 buckets, etc.
Use Protected Variables
Store sensitive information like cloud provider credentials
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GITLAB_USERNAME, and GITLAB_TOKEN as protected CI/CD variables.
Please see below guides:
Create a .gitlab-ci.yml file:
Define the CI/CD pipeline stages and terraform image.
# Global variables
variables:
TF_ROOT: "terraform"
STATE_FILE_NAME: "terraform.tfstate"
TF_ADDRESS: "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/terraform/state/${STATE_FILE_NAME}"
TF_USERNAME: ${GITLAB_USERNAME}
TF_PASSWORD: ${GITLAB_TOKEN}
default:
image:
name: hashicorp/terraform:latest
entrypoint: [""]
cache:
- key: "${CI_COMMIT_REF_SLUG}-terraform"
paths:
- $TF_ROOT/.terraform/
- $TF_ROOT/.terraform.lock.hcl
# Define stages
stages:
- init
- validate
- plan
- apply
- destroy
before_script:
- echo "Initializing Terraform in $TF_ROOT"
- export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- cd $TF_ROOT
terraform_init:
stage: init
script:
- terraform --version
- terraform init
-backend-config=address=${TF_ADDRESS}
-backend-config=lock_address=${TF_ADDRESS}/lock
-backend-config=unlock_address=${TF_ADDRESS}/lock
-backend-config=username=${TF_USERNAME}
-backend-config=password=${TF_PASSWORD}
-backend-config=lock_method=POST
-backend-config=unlock_method=DELETE
-backend-config=retry_wait_min=5
artifacts:
paths:
- $TF_ROOT/.terraform
- $TF_ROOT/.terraform.lock.hcl
expire_in: 1 hour
terraform_validate:
stage: validate
script:
- terraform fmt -check -recursive
- terraform validate
dependencies:
- terraform_init
terraform_plan:
stage: plan
script:
- terraform plan -out=tfplan
dependencies:
- terraform_validate
artifacts:
paths:
- $TF_ROOT/tfplan
expire_in: 1 hour
when: on_success
only:
- main
- /^feature\/.*$/
# Stage for applying the Terraform apply
terraform_apply:
stage: apply
script:
- terraform show tfplan
- terraform apply -auto-approve tfplan
artifacts:
paths:
- $TF_ROOT/terraform.tfstate
expire_in: 1 hour
when: manual
only:
- main
- /^feature\/.*$/
terraform_destroy:
stage: destroy
script:
- terraform destroy -auto-approve
when: manual
only:
- main
- /^feature\/.*$/
⚠️ Note : apply/destroy is not recommended on feature branches.
- Use terraform plan only on feature branches
- Gate apply/destroy behind merge to a protected branch
- Require approvals or deploy tags to trigger apply
- Use plan outputs to validate changes before applying
Amazon web console
Once you run terraform apply, Terraform provisions or updates your EC2 instance(s) based on the configuration in your .tf files. Here's what you'll see in the Amazon Web Console after a successful apply
Terraform destroy
After a terraform destroy, your EC2 instance is gone — but if you want to run a post-destroy job, such as cleanup tasks, notifications, or archiving logs, you’ll need to orchestrate it outside Terraform since Terraform doesn’t natively support post-destroy hooks.
✅ Easy Post-Destroy Steps
- Run a cleanup script after terraform destroy
- Remove leftover resources (like EBS volumes or Elastic IPs)
- Send a notification (Slack, email, etc.)
- Archive logs or Terraform state
- Delete secrets and SSH keys linked to the EC2 instance
- Validate in AWS Console that everything’s gone
- Optional: Use a wrapper script or CI/CD job to automate all this
Terraform Project Structure
repo-root/
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── ...
└── .gitlab-ci.yml
variables.tf
variable "region" {
description = "AWS region"
type = string
default = "ap-southeast-2"
}
variable "ami_id" {
description = "AMI ID for the EC2 instance"
default = "ami-0310483fb2b488153"
type = string
}
variable "instance_type" {
description = "Instance type for the EC2 instance"
type = string
default = "t2.micro"
}
main.tf
provider "aws" {
region = var.region
}
resource "aws_instance" "my_instance" {
ami = var.ami_id
instance_type = var.instance_type
}
outputs.tf
output "web_server_public_ip" {
value = aws_instance.my_instance.public_ip
description = "The public IP address of the web server."
}
🧩 Workflow Overview
1. terraform init
- Initializes working directory.
- Downloads required provider plugins (e.g., AWS).
- Sets up backend if remote state is configured.
2. terraform validate
- Validates
.tf
syntax and structure. - Ensures configuration is logically sound.
3. terraform plan
- Generates an execution plan.
- Displays actions Terraform will take:
- Create EC2 instance
- Attach security group
- Allocate volumes
- Allows review before making any changes.
4. terraform apply
- Executes the plan by interacting with AWS APIs.
- Creates the EC2 instance with specified parameters:
- AMI ID
- Instance type
- Key pair
- Subnet
- Applies tags and IAM roles (if defined).
- Prompts for approval unless
-auto-approve
is used.
5. 📡 Provisioning via AWS
- AWS provisions the instance in your chosen region and AZ.
- Network interfaces, volumes, and security configurations are applied.
- Terraform waits until the EC2 reaches the
running
state.
6. terraform state
update
-
.tfstate
file is modified to reflect actual infrastructure. - Stores metadata including EC2 instance ID, IP, etc.
- Essential for managing future changes and avoiding drift.
7. Output Variables (optional)
- Displays configured outputs:
- Public IP
- Instance hostname
- EC2 tags, etc.
References
🧠 AI Assistance — Content and explanations are partially supported by ChatGPT, Microsoft Copilot, and GitLab Duo, following AWS documentations.
Top comments (0)