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
| Repository | URI | Image Size | Service |
|---|---|---|---|
aiqlick-backend | 842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-backend | ~240MB | NestJS backend API |
background-tasks | 842697652860.dkr.ecr.eu-north-1.amazonaws.com/background-tasks | ~699MB | FastAPI AI/ML pipelines |
aiqlick-meeting | 842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-meeting | ~142MB | Custom Jitsi Meet frontend |
aiqlick-jigasi | 842697652860.dkr.ecr.eu-north-1.amazonaws.com/aiqlick-jigasi | ~505MB | Jigasi transcription gateway |
All four repositories are in the same AWS account (842697652860) and region (eu-north-1).
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 Pattern | Example | Meaning |
|---|---|---|
latest | latest | Most recent production build |
dev-latest | dev-latest | Most recent development build |
{sha} | a1b2c3d | Production build from specific Git commit |
dev-{sha} | dev-a1b2c3d | Development build from specific Git commit |
previous | previous | Previous production image (background-tasks only) |
Environment Mapping
| Git Branch | ECR Tag Prefix | Example Tags |
|---|---|---|
main | (none) | latest, a1b2c3d |
dev | dev- | dev-latest, dev-a1b2c3d |
Rollback Tag (background-tasks only)
The background-tasks repository maintains a :previous tag for automatic rollback. During deployment:
- The current
latestimage is re-tagged asprevious. - The new image is pushed as
latest. - If post-deployment health checks fail, the EC2 instance automatically reverts to the
previousimage.
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:
| Repository | Dockerfile Base | Multi-stage | Key Build Args |
|---|---|---|---|
aiqlick-backend | node:20-alpine | Yes | Prisma generate, production deps only |
background-tasks | python:3.11-slim | No | pip install from requirements.txt |
aiqlick-meeting | node:20-alpine | Yes | Webpack production build |
aiqlick-jigasi | maven + openjdk | Yes | Maven 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
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
| Factor | Detail |
|---|---|
| Storage pricing | $0.10 per GB per month (eu-north-1) |
| Data transfer | Free within same region (EC2 to ECR); standard rates for cross-region |
| Largest repository | background-tasks at ~699MB per image |
| Estimated monthly storage | ~20 images x 4 repos x ~400MB avg = ~32GB = ~$3.20/month without cleanup |
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.