You built an image on your laptop — now it needs to end up on a server. An image registry is exactly the place through which an image travels from one machine to another, and from which CI pulls and pushes images automatically.
What an image registry is
An image registry is a network-accessible store of Docker images. The idea is the same as a git repository: you send (push) an image to the registry, and then pull (pull) it onto any machine — locally, on a server, in Kubernetes.
The most common registries:
- Docker Hub (
hub.docker.com) — the default public registry. Free for public images; private ones are limited on the free plan. When you rundocker pull nginx, Docker goes here. - GitHub Container Registry (
ghcr.io) — GitHub's registry, convenient if your code is already there. Access is managed through GitHub tokens. - Private registries — you can run your own (for example, with
registry:2or Harbor) or use a cloud provider's registry (Amazon ECR, Google Artifact Registry, Yandex Container Registry).
They all work over a single protocol — the OCI Distribution Spec — so the docker push / docker pull commands are identical regardless of the registry.
Image naming and tags
A full image name looks like this:
<registry>/<namespace>/<name>:<tag>
A few examples:
nginx:1.27 # Docker Hub, official image
myuser/myapp:latest # Docker Hub, user image
ghcr.io/myorg/backend:v1.4.2 # GitHub Container Registry
registry.company.ru/team/api:sha-abc1234 # private registry
If no registry is specified — Docker Hub. If no tag is specified — latest.
A tag is simply a label on a specific image layer. Tags are mutable: latest today and latest a week from now may point to different images. That is why latest is convenient for development but dangerous in production.
Tagging strategy
Chaotic naming is a source of headaches: it is unclear what is deployed, hard to roll back, and difficult to trace which commit an image was built from.
Three durable strategies:
1. Semantic versioning (semver)
myapp:1.4.2
myapp:1.4
myapp:1
Human-readable, supports "floating" tags (1.4 → always the latest patch release). Suitable for libraries and public images.
2. Git commit SHA
myapp:sha-abc1234f
Unambiguously ties the image to the source code. Cannot be overwritten by accident. Recommended as the primary tag in CI — you always know which commit it was built from.
3. Combined
myapp:1.4.2 # for the release tag
myapp:sha-abc1234f # for precise tracking
You can put both tags on a single image.
Short rule of thumb: never use latest in production — only a versioned or sha tag.
docker tag, push, and pull
Before sending an image to a registry, log in to it:
docker login ghcr.io -u USERNAME --password-stdin <<< "$GITHUB_TOKEN"
An image is tagged with the docker tag command:
# build first
docker build -t backend:local .
# add the registry "address"
docker tag backend:local ghcr.io/myorg/backend:sha-abc1234f
docker tag backend:local ghcr.io/myorg/backend:v1.4.2
Push:
docker push ghcr.io/myorg/backend:sha-abc1234f
docker push ghcr.io/myorg/backend:v1.4.2
Pull on another machine:
docker pull ghcr.io/myorg/backend:sha-abc1234f
A single image (one set of layers) can have any number of tags — this is cheap: tags are just pointers, the data is not duplicated.
Building and publishing in CI
Manual builds and push are fine for experiments. In a real project, the image is built automatically on every push to the main branch or when a release tag is created.
The general pipeline scheme:
Code → git push → CI runs → docker build → docker tag → docker push → (deployment)
An example configuration for GitHub Actions (.github/workflows/build.yml):
name: Build and publish image
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # needed to publish to ghcr.io
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and publish
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
What happens here:
- On a push to
main, the job runs. - CI logs in to the registry using the automatic
GITHUB_TOKEN. - The image is built from the
Dockerfileat the repository root. - Two tags are assigned: the commit sha (stable) and
latest(for convenience during development). - The image is pushed to the registry.
In a real project, the next step usually follows right after — updating a Kubernetes manifest or calling kubectl rollout restart — but that already goes beyond Docker as a build tool.
A word on multi-stage builds and image size
If you use a multi-stage build, CI does not need to do anything special — docker build handles it. For Spring Boot it looks like this: in the first stage you compile the jar, and in the second you copy only the jar on top of a base eclipse-temurin:21-jre. Only the final, lightweight image ends up in the registry.
# --- build stage ---
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./gradlew bootJar -x test
# --- final image ---
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/build/libs/app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
Such an image takes up 200–300 MB instead of 600+ MB with the full JDK — and that is exactly what goes to the registry.
In short
- An image registry is a store through which an image travels between machines; the most popular ones: Docker Hub, GitHub Container Registry, private registries from cloud providers.
- The full image name:
<registry>/<namespace>/<name>:<tag>; without a registry — Docker Hub, without a tag —latest. - In production, use a specific tag (semver or commit sha), not
latest— otherwise it is unclear what is actually deployed. docker tagadds a new pointer to an already-built image; the data is not copied.- The CI pipeline:
build → tag → push; the next step is deployment (Kubernetes, systemd, Compose on a server). - In GitHub Actions,
GITHUB_TOKENis enough to publish toghcr.io— no extra secrets are needed.
What to read next
- Multi-stage builds and image layers — how layers work, why instruction order affects image size and build speed.
- Image best practices — security, a minimal base image, an unprivileged user.
- Running Spring Boot in Docker — a full example: Dockerfile, JVM configuration, Spring profiles.