CI/CD Pipelines for Harbor to ECR Migration and Terraform Plan
Practical GitHub Actions examples for Harbor to ECR image migration and Terraform plan checks on pull requests.
CI/CD Pipelines for Harbor to ECR Migration and Terraform Plan
CI/CD Pipelines for Harbor to ECR Migration and Terraform Plan
I recently worked on two pipelines that solved real delivery problems:
- Migrating container images from Harbor to AWS ECR
- Running Terraform
planautomatically on pull requests
Both pipelines were designed to be repeatable, auditable, and safe to run in shared environments.
Prerequisites
Before using these snippets, I set up the following:
- IAM role for Harbor to ECR migration workflow:
arn:aws:iam::<ACCOUNT_ID>:role/github-ecr-migration-role - IAM role for Terraform PR workflow:
arn:aws:iam::<ACCOUNT_ID>:role/github-terraform-plan-role - GitHub Actions secrets:
HARBOR_USERNAME,HARBOR_PASSWORD images.txtat repo root with one repository path per line:
1
2
3
platform/api
platform/worker
shared/frontend
- Terraform backend/state access already configured for the target environment
Pipeline 1: Harbor to ECR Migration
The objective was to move existing images from Harbor into ECR without rebuilding images or changing tags.
Approach
- Store source image list in a file (
images.txt) - Authenticate to Harbor and AWS using GitHub Actions secrets + OIDC
- Create ECR repositories if they do not exist
- Copy images directly registry-to-registry
- Keep logs for each migrated image
I used skopeo so the pipeline copies manifests/layers directly and keeps tags intact.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
name: harbor-to-ecr-migration
on:
workflow_dispatch:
inputs:
image_tag:
description: "Tag to migrate"
required: true
default: "latest"
permissions:
id-token: write
contents: read
jobs:
migrate:
runs-on: ubuntu-latest
env:
AWS_REGION: us-east-1
AWS_ACCOUNT_ID: "123456789012"
HARBOR_REGISTRY: harbor.example.com
ECR_REGISTRY: 123456789012.dkr.ecr.us-east-1.amazonaws.com
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-ecr-migration-role
aws-region: $
- name: Install skopeo
run: sudo apt-get update && sudo apt-get install -y skopeo
- name: Login to Harbor
run: echo "$" | skopeo login $HARBOR_REGISTRY -u "$" --password-stdin
- name: Login to ECR
run: aws ecr get-login-password --region $AWS_REGION | skopeo login $ECR_REGISTRY -u AWS --password-stdin
- name: Copy images Harbor -> ECR
run: |
TAG="$"
while read -r IMAGE; do
[ -z "$IMAGE" ] && continue
aws ecr describe-repositories --repository-names "$IMAGE" >/dev/null 2>&1 || \
aws ecr create-repository --repository-name "$IMAGE" >/dev/null
skopeo copy --all \
docker://$HARBOR_REGISTRY/$IMAGE:$TAG \
docker://$ECR_REGISTRY/$IMAGE:$TAG
echo "Migrated $IMAGE:$TAG"
done < images.txt
Pipeline 2: Terraform Plan on Pull Requests
This pipeline gives quick feedback before merge and keeps infrastructure review in the PR workflow.
What it does
- Runs on pull requests
- Executes
fmt,init,validate, andplan - Uploads
tfplanoutput as an artifact - Fails the PR if validation or plan fails
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
name: terraform-plan
on:
pull_request:
paths:
- "terraform/**"
permissions:
id-token: write
contents: read
jobs:
plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform/envs/dev
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-terraform-plan-role
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.8.5
- name: Terraform fmt
run: terraform fmt -check -recursive
- name: Terraform init
run: terraform init -input=false
- name: Terraform validate
run: terraform validate
- name: Terraform plan
run: terraform plan -input=false -no-color -out=tfplan
- name: Save plan output
run: terraform show -no-color tfplan > tfplan.txt
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: terraform-plan-dev
path: terraform/envs/dev/tfplan.txt
What I Learned
- OIDC is critical for both pipelines so we avoid long-lived credentials
- Migration jobs should be manually triggered and logged carefully
- Terraform
planin PRs improves quality beforeapply - Keep production
applyas a separate workflow with approvals
These two pipelines made migrations safer and IaC reviews faster for the team.
Related Posts
This post is licensed under CC BY 4.0 by the author.