Push Docker Images to a Registry
Learn how to push Docker images to a registry in this CI/CD foundations tutorial. Understand the core concept, follow hands-on steps, and explore troubleshooting tips.
Focus: push docker images to a registry
You've built an image that runs perfectly on your laptop, but when you try to run it on a server or share it with a teammate, you hit a wall — 'image not found' or worse, you have to rebuild everything from scratch. That's the pain of keeping Docker images local: your work is trapped on one machine. Pushing Docker images to a registry solves this by giving you a central, versioned home for your images, so any machine or pipeline can pull them instantly. In this lesson, you'll learn how to push Docker images to a registry, from tagging to authentication to the push command itself — and you'll do it hands-on with both Docker Hub and an alternative registry.
The problem this lesson solves
Imagine you've just finished a Dockerfile that packages your app with all its dependencies. Locally, docker build succeeds, and docker run works — everything is perfect. But then your CI/CD pipeline needs that image to deploy to staging, or your coworker wants to test it on their machine. Without a registry, you'd have to email a tar file or rebuild the image every time, which is slow and error-prone.
The core problem is image distribution. Docker images are large (hundreds of MB or more), and they're stored locally by default. To make them available across environments — development, staging, production — you need a centralized, versioned store. That's exactly what a container registry provides.
Why this matters now: In any real CI/CD pipeline, pushing images to a registry is a non-negotiable step. It's the bridge between 'build' and 'deploy'. Without it, your pipeline is stuck — you can't promote artifacts to other stages. Mastering this skill unblocks the rest of your delivery workflow.
Core concept / mental model
Think of a Docker registry as GitHub for your images. Just as you git push your code to a remote repository, you docker push your images to a remote registry. The registry stores image layers efficiently, deduplicating shared layers, and lets you tag versions so you can track exactly what's deployed where.
A simple mental model:
- Local images: stored in Docker's local storage on your machine, visible with
docker images. - Registry: a remote server (like Docker Hub, GitHub Container Registry, or a private registry) that stores images in a structured way.
- Push: uploads your local image (or just the missing layers) to the registry, making it available to anyone with access.
- Pull: downloads an image from the registry to a local machine — the reverse of push.
Key definitions to know:
- Image name: The name of your image, e.g.,
myapp. - Tag: A label for a specific version, e.g.,
latestorv1.0.0. - Repository: The registry path plus the image name, e.g.,
username/myapp. - Registry URL: The server hosting images. Docker Hub uses
docker.io; others use their own domains.
How it works step by step
Pushing an image to a registry involves a logical sequence: build a local image, tag it with the registry path, authenticate to the registry, push, and verify.
Step 1: Build the image locally
Before you can push, you need an image. Use docker build -t <name>:<tag> .. This creates a local image with a name you control.
Step 2: Tag the image for the registry
Docker images don't know where they're going until you tag them. The full image name in Docker follows this pattern:
[registry_url/][username/]image_name:tag
- If you omit the registry URL, Docker Hub is assumed (
docker.io). - If you omit the username, Docker treats it as an official image — which you can't push to if it's not yours. Always include your username or namespace.
So to push to your Docker Hub account, tag like this: docker tag myapp:latest username/myapp:latest.
Step 3: Authenticate with the registry
Registries require authentication to push. Use docker login <registry_url>. For Docker Hub, just docker login. Store your credentials securely — in CI, use a secret or a service account token.
Step 4: Push the image
Run docker push <full_image_name>. Docker uploads the image layers. If the image already exists partially in the registry, only the missing layers are uploaded (thanks to content-addressable storage).
Step 5: Verify the push
You can verify by pulling the same image on another machine, or browsing the registry web UI. Check that the tag exists and the digest matches.
Hands-on walkthrough
Let's walk through a complete example using Docker Hub (or any registry you have access to).
Prerequisites
- Docker installed and the daemon running
- A Docker Hub account (free) or access to a private registry
- Your terminal/CLI open
Example 1: Build, tag, and push to Docker Hub
First, create a simple Dockerfile:
FROM alpine:latest
CMD ["echo", "Hello from my pushed image!"]
Now build it locally:
docker build -t hello-world-app .
Check the image exists locally:
docker images
You'll see hello-world-app with tag latest.
Now tag it with your Docker Hub username. Replace yourusername with your actual username:
docker tag hello-world-app yourusername/hello-world-app:latest
Authenticate to Docker Hub (it will prompt for your username and password/personal access token):
docker login
export DOCKER_HUB_TOKEN="your_token" # or use a credential helper
echo "$DOCKER_HUB_TOKEN" | docker login --username yourusername --password-stdin
Pro tip: Always use
--password-stdinto avoid passing passwords on the command line. For CI/CD, store your token in a secret and use$DOCKER_HUB_TOKEN.
Now push:
docker push yourusername/hello-world-app:latest
Expected output (truncated):
The push refers to repository [docker.io/yourusername/hello-world-app]
<some-layer-id>: Pushed
<another-layer-id>: Pushed
latest: digest: sha256:... size: 1234
The digest is the content-based hash; it changes if the image content changes.
Example 2: Push with a version tag and to a non-Docker Hub registry
For versioning, tag with a semantic version:
docker tag hello-world-app yourusername/hello-world-app:1.0.0
docker push yourusername/hello-world-app:1.0.0
To push to GitHub Container Registry (ghcr.io), tag with the full registry URL:
docker tag hello-world-app ghcr.io/yourgithubname/hello-world-app:latest
docker login ghcr.io --username yourgithubname --password $GHCR_TOKEN
docker push ghcr.io/yourgithubname/hello-world-app:latest
Expected output similar to Docker Hub but with ghcr.io in the repository path.
Verify by pulling
On another machine or after clearing local cache, test the round trip:
docker rmi yourusername/hello-world-app:latest
docker pull yourusername/hello-world-app:latest
docker run yourusername/hello-world-app:latest
Output: Hello from my pushed image!
Compare options / when to choose what
There are several registries, each with trade-offs. Here's a comparison table to help you choose:
| Registry | Use case | Cost | Key features |
|---|---|---|---|
| Docker Hub | Public/private images for individuals and small teams | Free public repos; paid private | Most common, built-in automation, easy integrations |
| GitHub Container Registry (ghcr.io) | If you're already on GitHub for code | Free for public; paid for private | Tight GitHub integration, fine-grained permissions |
| Amazon ECR | AWS production deployments | Pay for storage and data transfer | IAM integration, lifecycle policies, regional |
| Google Artifact Registry | GCP environments | Pay for storage | GCP-native, multi-format (Docker, Maven, etc.) |
| Self-hosted (e.g., Harbor, registry:2) | Air-gapped networks, full control | Infrastructure cost | Full control, audit logs, vulnerability scanning |
When to choose what: - For learning and quick demos, Docker Hub is the fastest to set up. - For a GitHub-centric workflow, ghcr.io reduces context switching. - For cloud deployments, use the registry that matches your cloud provider (ECR, ACR, or Artifact Registry) to minimize egress costs. - For compliance or air-gapped environments, go self-hosted.
Troubleshooting & edge cases
Error: "unauthorized: authentication required"
You tried to push without logging in or your token is invalid.
- Run
docker loginagain with correct credentials. - In CI, check that your secret is set and exported.
- Ensure the username in the image tag matches the authenticated user.
Error: "denied: requested access to the resource is denied"
You're pushing to a repository you don't own or don't have write access to.
- Verify the image tag includes your correct user/org name.
- Create the repository first if required (some registries auto-create on first push).
- Check the user has
writepermissions (for orgs).
Error: "token response error" or "invalid username/password"
- Make sure you're using the correct token (NOT your password — many registries enforce personal access tokens).
- For CI, escape
$if needed:echo "$TOKEN" | docker login ....
Push is very slow
- You might be pushing large layers. Use
.dockerignoreto keep the build context small. - Check your network speed; use a registry in the same region as your CI runner.
Image not found after push
- You may have pushed to a different namespace than you think. Run
docker image inspect <image>:<tag>and check theRepoTags. - Confirm you're pulling the correct tag:
latestmight not be what you expect. Always use explicit version tags for production.
"no space left on device" during push
- Clean up local images and unused layers:
docker system prune -a(careful — removes all unused images).
Tagging mistakes
- Forgetting the username means Docker treats it as an official image. Always include
youruser/. - Using
latestfor immutable deployments leads to drift; prefer semantic tags likev1.0.0and manage promotions manually.
What you learned & what's next
You now understand the core problem of image distribution, the mental model of a registry as a remote image store, and the full workflow: build → tag → login → push → verify. You've pushed your first image to Docker Hub (or another registry) and learned a round-trip pull to confirm it works. This is a critical capability in any CI/CD pipeline — it's the step that moves your built artifact from 'local success' to 'production-ready'.
What's next: In the next lesson in this track, you'll learn how to automate this push process inside a CI/CD pipeline using GitHub Actions. You'll connect your Docker build to a registry automatically with GitHub Actions' built-in docker/login-action and docker/build-push-action, and then trigger deployments based on the pushed image. You'll apply the same tagging strategy you used here to version your artifacts correctly.
Practice recap
Open your terminal and push an image from the last project you built. Tag it with yourusername/your-app:v1.0.0, push, then pull it back after removing it locally to confirm the round trip. For extra credit, try pushing the same image to GitHub Container Registry and note the differences in tags and login commands.
Common mistakes
- Forgetting to include your username or namespace in the image tag — Docker then assumes an official image, and the push is denied.
- Using
latestas the only tag for production — it creates ambiguity, and it's hard to roll back or trace which digest is running. - Passing passwords as command-line args for
docker loginin shell history or CI logs — always use--password-stdin. - Pushing without running
docker login(or in CI, without exporting the token from secrets) results in cryptic 'unauthorized' errors.
Variations
- Use a credential helper like
docker-credential-secretserviceon Linux or OSX keychain to avoid typing tokens manually. - Tag your images with a Git SHA (
git rev-parse --short HEAD) for immutable, traceable builds, and uselatestonly for convenience in dev. - For multi-architecture images, use
docker buildx build --platform linux/amd64,linux/arm64 -t ... --pushto push both architectures with a single tag.
Real-world use cases
- A CI pipeline builds a service image and pushes it to ghcr.io, then a deployment job pulls that exact digest for staging.
- A team uses Amazon ECR with IAM roles, and every code merge produces a versioned image automatically, enabling instant rollback.
- An air-gapped enterprise runs a self-hosted Harbor registry, and the release pipeline pushes images manually after security scanning.
Key takeaways
- A container registry is a central store for Docker images, enabling distribution across any environment.
- The push workflow is always build → tag (with registry + username) → authenticate → push → verify.
- Always include your namespace in the image tag to avoid confusion with official images.
- Use explicit version tags (like v1.0.0) and avoid relying on
latestfor anything beyond development. - Authenticate with
docker loginusing--password-stdinand treat tokens as secrets — especially in CI. - Verify a successful push by pulling the image on another machine or checking the registry UI.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.