Skip to main content

ECR (Container Registry)

All Docker images for AIQLick services are stored in Amazon Elastic Container Registry (ECR) in eu-north-1. GitHub Actions builds and pushes images on every deployment, and EC2 instances pull from ECR at container startup.

Repositories

RepositoryURIImage SizeService
aiqlick-backend842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-backend~240MBNestJS backend API
background-tasks842697652860.dkr.ecr.eu-north-1.amazonaws.com/background-tasks~699MBFastAPI AI/ML pipelines
aiqlick-meeting842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-meeting~142MBCustom Jitsi Meet frontend
aiqlick-jigasi842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-jigasi~505MBJigasi transcription gateway

All four repositories are in the same AWS account (842697652860) and region (eu-north-1).

note

The background-tasks image is the largest at ~699MB because it includes Python ML dependencies (LangChain, LangGraph, NumPy, etc.) and the AWS SDK packages for Bedrock, Rekognition, and Transcribe.

Image Tagging Strategy

ECR images follow a consistent tagging convention that encodes both the target environment and the specific commit.

Tag Format

Tag PatternExampleMeaning
latestlatestMost recent production build
dev-latestdev-latestMost recent development build
{sha}a1b2c3dProduction build from specific Git commit
dev-{sha}dev-a1b2c3dDevelopment build from specific Git commit
previouspreviousPrevious production image (background-tasks only)

Environment Mapping

Git BranchECR Tag PrefixExample Tags
main(none)latest, a1b2c3d
devdev-dev-latest, dev-a1b2c3d

Rollback Tag (background-tasks only)

The background-tasks repository maintains a :previous tag for automatic rollback. During deployment:

  1. The current latest image is re-tagged as previous.
  2. The new image is pushed as latest.
  3. If post-deployment health checks fail, the EC2 instance automatically reverts to the previous image.

This rollback mechanism is only implemented for background-tasks due to the complexity of its AI/ML dependencies and the higher risk of runtime failures.

Image Build and Push

Images are built and pushed by GitHub Actions as part of the CI/CD pipeline. The typical workflow:

Git push to dev/main
--> GitHub Actions triggers
--> Assumes github-actions-role (OIDC)
--> Authenticates with ECR
--> Builds Docker image
--> Tags with {env-}latest and {env-}{sha}
--> Pushes to ECR
--> Deploys to target EC2 instance

Build Context

Each project has a Dockerfile in its repository root:

RepositoryDockerfile BaseMulti-stageKey Build Args
aiqlick-backendnode:20-alpineYesPrisma generate, production deps only
background-taskspython:3.11-slimNopip install from requirements.txt
aiqlick-meetingnode:20-alpineYesWebpack production build
aiqlick-jigasimaven + openjdkYesMaven build, JAR assembly

Most images use multi-stage builds to minimize final image size -- build dependencies are discarded in the production stage.

Authentication

ECR access is controlled through IAM. No static credentials or tokens are stored anywhere.

EC2 Instances (Pull Only)

ECS Fargate authenticates to ECR using the execution role of each task — aiqlick-ecs-execution-role-{dev,prod} — which carries ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:GetDownloadUrlForLayer, and ecr:BatchGetImage. The Jitsi EC2 still pulls from ECR via SSM-driven aws ecr get-login-password flows on the host (operator credential, not an instance-profile flow). The legacy aiqlick-{backend,ai}-ec2-profile flows were retired with the Phase 7 cleanup.

# On EC2: authenticate Docker with ECR (token valid for 12 hours)
aws ecr get-login-password --region eu-north-1 | \
docker login --username AWS --password-stdin \
842697652860.dkr.ecr.eu-north-1.amazonaws.com

GitHub Actions (Push and Pull)

The github-actions-role includes the AmazonEC2ContainerRegistryPowerUser managed policy, allowing both push and pull operations. Authentication uses OIDC federation -- no AWS access keys are stored in GitHub.

# In GitHub Actions workflow
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::842697652860:role/github-actions-role
aws-region: eu-north-1

- uses: aws-actions/amazon-ecr-login@v2

Useful CLI Commands

Authenticate Docker with ECR

aws ecr get-login-password \
--profile Administrator-842697652860 \
--region eu-north-1 | \
docker login --username AWS --password-stdin \
842697652860.dkr.ecr.eu-north-1.amazonaws.com

List Repositories

aws ecr describe-repositories \
--profile Administrator-842697652860 \
--region eu-north-1 \
--query 'repositories[].{Name:repositoryName,URI:repositoryUri}' \
--output table

View Recent Images

# List the 5 most recent images for a repository
aws ecr describe-images \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--query 'imageDetails | sort_by(@, &imagePushedAt) | [-5:].[imageTags[0],imageSizeInBytes,imagePushedAt]' \
--output table

# View all tags for a specific repository
aws ecr describe-images \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name background-tasks \
--query 'imageDetails | sort_by(@, &imagePushedAt) | [-10:]' \
--output table

Pull an Image Locally

# Authenticate first (see above), then pull
docker pull 842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-backend:latest
docker pull 842697652860.dkr.ecr.eu-north-1.amazonaws.com/background-tasks:dev-latest

Push an Image (manual, for emergency deployments)

# Build and tag
docker build -t aiqlick-backend:latest .
docker tag aiqlick-backend:latest \
842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-backend:latest

# Push
docker push 842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-backend:latest
warning

Manual pushes bypass the CI/CD pipeline and skip automated tests and health checks. Use this only for emergency fixes. Normal deployments should always go through GitHub Actions.

Check Image Scan Results

aws ecr describe-image-scan-findings \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--image-id imageTag=latest \
--output json

Image Lifecycle and Cleanup

ECR charges per GB of stored images. Over time, old images accumulate as each deployment pushes new tags. Periodic cleanup is recommended to control costs.

Current State

Each deployment creates at least two tags per image (e.g., latest and a1b2c3d). With multiple deployments per day across four repositories and two environments, image storage can grow quickly.

Lifecycle Policy

ECR supports lifecycle policies to automatically expire old images. A recommended policy keeps the most recent tagged images and removes untagged images after a set period:

{
"rules": [
{
"rulePriority": 1,
"description": "Remove untagged images older than 7 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Keep only the 20 most recent tagged images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["dev-"],
"countType": "imageCountMoreThan",
"countNumber": 20
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 3,
"description": "Keep only the 20 most recent production images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["latest", "previous"],
"countType": "imageCountMoreThan",
"countNumber": 20
},
"action": {
"type": "expire"
}
}
]
}

Apply a Lifecycle Policy

aws ecr put-lifecycle-policy \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--lifecycle-policy-text file://lifecycle-policy.json

Manual Cleanup

# Delete a specific image by tag
aws ecr batch-delete-image \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--image-ids imageTag=dev-oldsha123

# Delete all untagged images in a repository
aws ecr describe-images \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--filter tagStatus=UNTAGGED \
--query 'imageDetails[].imageDigest' \
--output text | \
tr '\t' '\n' | \
xargs -I {} aws ecr batch-delete-image \
--profile Administrator-842697652860 \
--region eu-north-1 \
--repository-name aiqlick-backend \
--image-ids imageDigest={}

Cost Considerations

FactorDetail
Storage pricing$0.10 per GB per month (eu-north-1)
Data transferFree within same region (EC2 to ECR); standard rates for cross-region
Largest repositorybackground-tasks at ~699MB per image
Estimated monthly storage~20 images x 4 repos x ~400MB avg = ~32GB = ~$3.20/month without cleanup
tip

Setting up lifecycle policies on all four repositories is the simplest way to keep ECR costs under control. The policies run automatically and require no ongoing maintenance.