Cloud Container Registry & Image Management
Manage container images securely. Covers registry selection, image scanning, signing, layer caching, multi-arch builds, and image lifecycle policies.
A container registry is the artifact store for your container images. Every deployment pulls images from the registry — if it’s compromised or unavailable, nothing deploys. Image management includes scanning for vulnerabilities, signing for integrity, lifecycle policies to control storage costs, and multi-stage builds to minimize image size.
Registry Options
| Registry | Type | Best For |
|---|---|---|
| Amazon ECR | Managed | AWS-native workloads |
| Google Artifact Registry | Managed | GCP-native, multi-format |
| Azure Container Registry | Managed | Azure-native workloads |
| GitHub Container Registry | Managed | Open source, GitHub Actions CI |
| Docker Hub | Managed (public) | Public images, rate-limited |
| Harbor | Self-hosted | Air-gapped, full control |
Image Build Best Practices
# Multi-stage build: separate build and runtime
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Runtime stage: minimal image
FROM node:20-alpine AS runtime
WORKDIR /app
# Non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
# Copy only production artifacts
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
| Practice | Before | After |
|---|---|---|
| Multi-stage build | 1.2 GB image | 180 MB image |
| Alpine base | 950 MB (Debian) | 180 MB (Alpine) |
| Non-root user | Runs as root (security risk) | Runs as non-root |
| .dockerignore | Copies node_modules, .git | Only production code |
Image Scanning
# GitHub Actions: scan on every build
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1 # Fail CI if critical/high vulnerabilities found
- name: Push only if scan passes
run: |
docker tag myapp:${{ github.sha }} registry/myapp:${{ github.sha }}
docker push registry/myapp:${{ github.sha }}
Lifecycle Policies
{
"rules": [
{
"description": "Keep last 10 tagged images",
"selection": {"tagStatus": "tagged", "countType": "imageCountMoreThan", "countNumber": 10},
"action": {"type": "expire"}
},
{
"description": "Delete untagged images after 7 days",
"selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7},
"action": {"type": "expire"}
}
]
}
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
latest tag in production | No version pinning, non-reproducible | SHA or semver tags (v1.2.3) |
| No image scanning | Deploy images with known CVEs | Scan in CI, block critical/high |
| Giant images (1GB+) | Slow pulls, more attack surface | Multi-stage builds, Alpine base |
| Running as root | Container escape = host root access | USER directive, non-root |
| No lifecycle policy | Registry grows forever, costs increase | Expire untagged after 7 days |
Checklist
- Private registry for all production images
- Multi-stage Dockerfile: build and runtime stages separate
- Image size: < 200 MB for most applications
- Non-root user in Dockerfile (
USER) - Image scanning: Trivy or equivalent in CI
- No
latesttag: use SHA or semver for production - Lifecycle policy: expire old/untagged images
- Image signing: Cosign or Notary for integrity
- Pull-through cache: reduce external registry dependency
:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For container platform consulting, visit garnetgrid.com. :::