How to automate Terraform deployments with GitHub Actions.

Search for a command to run...

No comments yet. Be the first to comment.
Everything is simple when we write a playbook that does one thing. We have a powerful tool for expressing dependencies: the task order. But sometimes we want more than one target. We want to deploy something, then cleanup something. Or even build som...

Don’t want use the default one anymore? Deleted the default one? Good. Let’s do it. Actually it is minimal excepting one detail, my fetish - IPv6 support. VPC Of course, to create a VDP - we need VDP: resource "aws_vpc" "min_vpc" { cidr_block ...

It may happen that you starting working on a Terraform project in a team and local state file doesn’t work for you anymore. Time to a remote backed. It’s not going to be complete documentation. Or better than others. But maybe I’ll manage to hit your...

So. You have you playbook in git and you want to keep some variables invisible to others. You probably should go for a ‘real’ password manager like Hashicorp Vault. Or pass (https://www.passwordstore.org/). Both have ready to use lookup plugins in An...

I know… There is lots of articles on this rather basic topic. I’m not consider myself an expert. Maybe someone find helpful a HOWTO on this particular combination of technologies.
I’m going to create a simple workflow for deploying a Terraform stack on AWS using GitHub Action. I will setup OIDC authentication between GitHub and AWS.
We will need:
a simple, working Terraform project,
an AWS account.
a GitHub account.
Please, create it according to the guide: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws
It needs to be done once per AWS account.
It’s not going to work with a local state file. Let’s prepare all required items. I’m going to use Terraform code. Please mind it is not a part of the project you’re going to deploy.
Just create a bucket with permissions.
resource "aws_s3_bucket" "state-bucket" {
bucket = local.bucket_name
}
resource "aws_s3_bucket_versioning" "vers" {
bucket = aws_s3_bucket.state-bucket.id
versioning_configuration {
status = "Enabled"
}
}
I have a local variable describing the bucket name.
I’m going to use the same permissions for backend and for AWS provider. You probably should separate these roles for non-lab environments.
Despite - we’re going to use OIDC - I still want and user to run it locally.
data "aws_iam_policy_document" "project_policy" {
statement {
actions = [
"s3:ListBucket"
]
resources = [aws_s3_bucket.state-bucket.arn]
}
statement {
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
resources = [
"${aws_s3_bucket.state-bucket.arn}/*/terraform.tfstate",
"${aws_s3_bucket.state-bucket.arn}/*/terraform.tfstate.tflock"
]
}
}
resource "aws_iam_user" "project_user" {
name = "${var.project_name}-user"
}
resource "aws_iam_user_policy" "project_user_policy" {
user = aws_iam_user.project_user.id
policy = data.aws_iam_policy_document.project_policy.json
}
Now - add the permissions which allow to build you infra. They are a little bit too wide. Please adjust them to your needs.
resource "aws_iam_user_policy_attachment" "ec2-full" {
user = aws_iam_user.project_user.id
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}
resource "aws_iam_user_policy_attachment" "vpc-full" {
user = aws_iam_user.project_user.id
policy_arn = "arn:aws:iam::aws:policy/AmazonVPCFullAccess"
}
resource "aws_iam_user_policy_attachment" "ssm-ro" {
user = aws_iam_user.project_user.id
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess"
}
resource "aws_iam_user_policy_attachment" "iam-ro" {
user = aws_iam_user.project_user.id
policy_arn = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
}
You will need an access key for the user to use it locally
OIDC needs an IAM role.
data "aws_iam_policy_document" "sts_policy" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = ["arn:aws:iam::<AWS account ID>:oidc-provider/token.actions.githubusercontent.com"]
}
actions = ["sts:AssumeRoleWithWebIdentity"]
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:<GitHub account>/<Repo>:ref:refs/heads/main"
]
}
}
}
resource "aws_iam_role" "project_role" {
assume_role_policy = data.aws_iam_policy_document.sts_policy.json
name = "${var.project_name}-role"
}
resource "aws_iam_role_policy" "project_role_attach" {
role = aws_iam_role.project_role.id
policy = data.aws_iam_policy_document.project_policy.json
}
You need to adjust the following parameters:
AWS account ID,
GitHub user or organization,
The repo name.
The last resource allows the role to access the bucket. You still need the resources access. You can copy the code from user changing aws_iam_user_policy_attachment to aws_iam_role_policy_attachment.
That’s it. You can deploy the resources manually.
Now create or update the backend setup for your project.
terraform {
backend "s3" {
bucket = "<bucket-name>"
key = "<stack>/terraform.tfstate"
use_lockfile = true
region = "eu-central-1"
}
}
Replace bucket name, and “folder” in the bucket. I have multiple stacks in my project - each with a separate state. If your project is live already, use terraform init -migrate-state. If i’s a fresh one - just terraform init'
Crate a YAML file in .github/workflows/
---
name: Net
on:
push:
branches: ["main"]
paths:
- net/**
permissions:
id-token: write
contents: read
jobs:
make_key:
runs-on: ubuntu-latest
defaults:
run:
working-directory: net/
steps:
- uses: actions/checkout@v6
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::<AWS account id>:role/tf-infra-role
aws-region: eu-central-1
- uses: hashicorp/setup-terraform@v3
- run: terraform init -input=false
- run: terraform plan
- run: terraform apply -auto-approve
A quick explanation:
the workflow runs when you push to the ‘main’ and something in net/ subdir is changed. As I said - I have multiple stacks with multiple workflows.
“permissions” - required to handle OIDC.
“working-directory” - skip it if you run your project in the root.
“uses: aws-actions/configure-aws-credentials” - setups AWS connectivity. Use your actual account id.
Then terraform stuff goes. I know. I should have an approval after plan…
If everything went right - after next push you infra should built. You always can run terraform destroy locally.
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp.html
https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws