24.9. Container Registries
Table of Contents
Understanding Container Registries
A container registry is a service where you store and share container images. It plays a similar role to Git hosting platforms for source code, but for built images.
When you build Docker images in CI, the registry is usually the bridge between your CI pipeline and your deployment environment.
What Is a Container Registry?
A container registry is a centralized storage for container images, typically supporting:
- Pushing images (upload)
- Pulling images (download)
- Tagging and versioning
- Access control (who can push or pull)
- Scanning for vulnerabilities
- Replication and caching
You interact with a registry using docker or compatible tools.
Example:
# Login to registry
docker login registry.example.com
# Build image
docker build -t registry.example.com/my-app:1.0.0 .
# Push image
docker push registry.example.com/my-app:1.0.0
# Later, on a server
docker pull registry.example.com/my-app:1.0.0
docker run -d --name my-app registry.example.com/my-app:1.0.0
A container registry is not the same as Docker Hub.
Docker Hub is one implementation of a registry. You can use many different registries or even run your own.
Image Names and Tags
Every image in a registry has a name and a tag. The full image reference usually looks like this:
[registry-host]/[namespace]/[repository]:[tag]Common patterns:
| Example | Meaning |
|---|---|
python:3.12 | Docker Hub, library/python, tag 3.12 |
myuser/myapp:latest | Docker Hub, namespace myuser, repo myapp, tag latest |
ghcr.io/my-org/my-api:1.0.0 | GitHub Container Registry, org my-org, repo my-api |
registry.example.com/myteam/app:2024-08-01 | Private registry at registry.example.com |
Useful conventions:
- Use semantic versioning:
1.0.0,1.0.1,2.0.0. - Use build metadata:
1.0.0-commit-sha,1.0.0-2024-08-01. - Avoid relying only on
latestin production.
Rule: Always tag images explicitly in CI, for example using $GIT_SHA or a release version.
Never deploy untagged or implicitly tagged latest images to production.
Example CI tags:
IMAGE_TAG="$GITHUB_SHA" # full commit SHA
IMAGE_TAG="1.0.0" # release
IMAGE_TAG="1.0.0-$GITHUB_SHA" # release + SHATypes of Container Registries
There are several common types of registries you will encounter.
Public Registries
Public registries host images that are often accessible to everyone.
Examples:
| Registry | Example image | Notes |
|---|---|---|
| Docker Hub | nginx:1.27 | Most common public registry |
| GitHub Container Registry | ghcr.io/user/app:tag | Integrated with GitHub |
| GitLab Registry | registry.gitlab.com/group/app | Integrated with GitLab |
Typical use cases:
- Base images such as
python,nginx,postgres. - Open source application images.
Private Registries
Private registries are used for internal or proprietary images.
Options:
| Option | Description |
|---|---|
| Private repos on Docker Hub | Private images under your Docker account |
| GitHub Packages / GHCR | Private images tied to GitHub repos |
| GitLab Container Registry | Private images tied to GitLab projects |
| Cloud provider registries | ECR, GCR, ACR etc |
| Self-hosted registry | Your own registry instance, often via Docker |
Use a private registry when:
- Your code is proprietary.
- You need fine-grained access control.
- You want images close to your deployment region to reduce latency.
Popular Registry Providers
Many CI/CD setups use registries that are built into source hosting or cloud platforms.
Docker Hub
- Default registry for Docker CLI.
- Image names like
user/app:tag. - Public by default, can be private.
- Useful for:
- Public open source projects.
- Simple personal projects.
Example usage:
# Logged-in as 'mydockeruser'
docker build -t mydockeruser/my-api:1.0.0 .
docker push mydockeruser/my-api:1.0.0GitHub Container Registry (GHCR)
- Registry host:
ghcr.io. - Integrates with GitHub Actions and repo permissions.
- Images tied to GitHub organizations and repositories.
Example naming conventions:
| Pattern | Description |
|---|---|
ghcr.io/OWNER/IMAGE_NAME:tag | General image |
ghcr.io/OWNER/REPO/IMAGE_NAME | Sometimes used inside org repos |
Minimal GitHub Actions example:
name: Build and push image
on:
push:
branches: [ main ]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}/my-api:${{ github.sha }}GitLab Container Registry
- Integrated per GitLab project.
- Registry host typically
registry.gitlab.com. - Image path usually follows project path.
Example name:
registry.gitlab.com/my-group/my-project/backend:1.0.0Minimal GitLab CI snippet:
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_DRIVER: overlay2
build_and_push:
stage: build
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"Cloud Provider Registries
Cloud platforms provide registries that integrate with their services:
| Provider | Registry | Example image |
|---|---|---|
| AWS | Amazon ECR | 123456789012.dkr.ecr.us-east-1.amazonaws.com/app:tag |
| GCP | Artifact Registry / GCR | us-docker.pkg.dev/project/repo/app:tag |
| Azure | Azure Container Registry (ACR) | myregistry.azurecr.io/app:tag |
Common use case:
- Build image in CI, push to cloud registry, then deploy to Kubernetes or container services within the same cloud.
Authentication and Access Control
To push to most registries, you must authenticate. To pull, you may or may not need authentication, depending on whether the image is public.
Basic Docker Login
For many registries you can login with docker login:
docker login registry.example.com
# Prompts for username and password or tokenThen build and push:
docker build -t registry.example.com/myteam/app:1.0.0 .
docker push registry.example.com/myteam/app:1.0.0CI Authentication With Secrets
In CI you should never hardcode credentials in the pipeline file. Use secret variables.
Example with GitHub Actions and Docker Hub:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}Example with GitHub Actions and a private registry:
- name: Docker login
uses: docker/login-action@v3
with:
registry: registry.example.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}Example with environment variables in GitLab:
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
Rule: Store registry credentials in CI secrets, never in the repository.
Use tokens or service accounts instead of personal passwords when possible.
Using Registries in CI/CD Pipelines
In a typical CI/CD workflow, the registry connects the build and deploy stages:
- Build job in CI:
- Checkout code.
- Build Docker image.
- Tag image with version or commit SHA.
- Push image to registry.
- Deploy job in CI or CD:
- Pull specific image tag from registry.
- Update Kubernetes deployment, Docker Compose, or server to use that tag.
Example: Simple Build and Push Pipeline
GitHub Actions example:
name: Build and push image
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: mydockeruser/my-api:${{ github.sha }}
A deploy job could then reference mydockeruser/my-api:${{ github.sha }}.
Example: Multiple Tags for the Same Image
You can apply several tags to the same image, for example commit SHA and branch name:
IMAGE_NAME="registry.example.com/myteam/app"
COMMIT_SHA="$GITHUB_SHA"
BRANCH_NAME="${GITHUB_REF_NAME}"
docker build -t "$IMAGE_NAME:$COMMIT_SHA" .
docker tag "$IMAGE_NAME:$COMMIT_SHA" "$IMAGE_NAME:$BRANCH_NAME"
docker tag "$IMAGE_NAME:$COMMIT_SHA" "$IMAGE_NAME:latest"
docker push "$IMAGE_NAME:$COMMIT_SHA"
docker push "$IMAGE_NAME:$BRANCH_NAME"
docker push "$IMAGE_NAME:latest"This way:
- Production may use a frozen version like
:1.2.3. - Staging might follow
:main. - Local testing can use
:latest.
Self-Hosted Registries
Sometimes you want to run your own registry, for example inside a corporate network.
Basic Docker Registry
Docker provides a reference implementation you can run as a container:
docker run -d \
-p 5000:5000 \
--name registry \
registry:2
You can then push images to localhost:5000:
docker build -t localhost:5000/my-app:1.0.0 .
docker push localhost:5000/my-app:1.0.0This is useful for:
- Local development.
- Internal use in a private network.
For production use, you typically add:
- Authentication.
- TLS via reverse proxy.
- Storage backends (S3, etc).
Using a Private Registry in CI
If your registry is at registry.internal:5000, your CI configuration might include:
- name: Docker login
run: echo "$REGISTRY_PASSWORD" | docker login registry.internal:5000 -u "$REGISTRY_USER" --password-stdin
- name: Build
run: docker build -t registry.internal:5000/my-app:${GITHUB_SHA} .
- name: Push
run: docker push registry.internal:5000/my-app:${GITHUB_SHA}
Note that the CI runners must have network access to registry.internal:5000.
Security and Best Practices
When dealing with container registries in CI/CD you must think about:
Keep Images Private When Needed
- Make private images private in the registry.
- Restrict who can push and who can pull.
- Use organization or project scoped permissions.
Avoid Leaking Credentials
- Store registry credentials in CI secrets.
- Use token-based authentication where possible.
- Rotate tokens periodically.
- Avoid printing tokens or passwords in CI logs.
Example of a bad practice:
# BAD: Hardcoded password
- run: docker login -u user -p mysecretpasswordExample of a safer practice:
- run: echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdinImage Scanning
Many registries support vulnerability scanning:
- GitHub Container Registry security features.
- Docker Hub scanning (for some plans).
- Cloud provider registries with built-in scanners.
In CI you can:
- Fail builds if critical vulnerabilities are found.
- Schedule regular scans on existing images.
Retention and Cleanup
Images can quickly consume storage. Good policies:
- Automatically delete old tags that are no longer deployed.
- Keep recent images per branch or per environment.
- Keep release tags indefinitely, or based on business requirements.
Example policy:
| Tag type | Example | Retention |
|---|---|---|
| Commit tags | :sha-1234abcd | Last 30 days or 100 images |
| Branch tags | :main | Keep latest only |
| Release tags | :1.0.0 | Keep all or manual cleanup |
Connecting Registries to Deployment Environments
Your deployment environment must be able to pull images from your registry.
Docker Compose
In a docker-compose.yml file:
services:
api:
image: registry.example.com/myteam/app:1.0.0
ports:
- "8000:8000"
Before docker-compose up, you might need:
docker login registry.example.comKubernetes
For Kubernetes, you define the image in the Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
spec:
replicas: 2
selector:
matchLabels:
app: my-api
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: my-api
image: registry.example.com/myteam/app:1.0.0
ports:
- containerPort: 8000For private registries you typically configure:
- ImagePullSecrets referencing registry credentials.
- Or cloud identity mechanisms for cloud registries.
By understanding container registries and how to integrate them into CI/CD pipelines, you can:
- Consistently build, version, and distribute images.
- Separate build from deploy stages.
- Securely manage your application artifacts across environments.
Views: 10
KAHIBARO