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

Sponsored

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., latest or v1.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-stdin to 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 login again 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 write permissions (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 .dockerignore to 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 the RepoTags.
  • Confirm you're pulling the correct tag: latest might 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 latest for immutable deployments leads to drift; prefer semantic tags like v1.0.0 and 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 latest as 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 login in 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

  1. Use a credential helper like docker-credential-secretservice on Linux or OSX keychain to avoid typing tokens manually.
  2. Tag your images with a Git SHA (git rev-parse --short HEAD) for immutable, traceable builds, and use latest only for convenience in dev.
  3. For multi-architecture images, use docker buildx build --platform linux/amd64,linux/arm64 -t ... --push to 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 latest for anything beyond development.
  • Authenticate with docker login using --password-stdin and treat tokens as secrets — especially in CI.
  • Verify a successful push by pulling the image on another machine or checking the registry UI.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.