| |

Docker 12 🐳 Pulling, Tagging, and Pushing Images to Docker Hub and Private Registries

Docker images are portable only when they are stored in a registry. The local daemon holds images built or pulled on a machine, but those images are not available to other machines, to CI runners, or to a Kubernetes cluster until they are pushed to a registry. Docker Hub is the default public registry, and it is the simplest place to share an image. Private registries serve the same purpose within an organization, adding authentication, access control, and network isolation.

The workflow is consistent across registries: authenticate with docker login, tag the image with the registry’s address and the target repository name, and push. The tag is not just a label; it is the image’s fully qualified name, and it determines where the push goes. A local image named my-app has no registry component, so docker push my-app fails. The same image tagged myusername/my-app:v1.0 can be pushed to Docker Hub because the first component of the name identifies the account.

This chapter covers the image naming convention, authenticating to Docker Hub and private registries, tagging images for push, pushing to Docker Hub, and the patterns for private registry configuration.

Key point: An image name has three parts: registry/namespace/repository:tag. Docker Hub uses docker.io implicitly, and the namespace is the Docker Hub username or organization. A local image must be tagged with the registry, namespace, and repository before it can be pushed. docker login stores credentials in the configured credential store, and docker push uploads the image layers and manifest to the registry.


Why tagging and pushing matter

The portability problem. An image on a local daemon is not portable. To run it on another machine, that machine must pull it from a registry. The push is what makes the image available.

The naming problem. Docker uses the image name to determine the registry. An image named nginx is implicitly docker.io/library/nginx. An image named myregistry.com:5000/myapp is explicitly on myregistry.com:5000. The name is the routing instruction, and getting it wrong sends the image to the wrong place or fails.

The authentication problem. Docker Hub requires authentication to push, and private registries require credentials to pull and push. docker login stores those credentials so subsequent commands can use them without re-entering them.

The access-control problem. Private registries add authentication and authorization: who can pull, who can push, and which repositories they can access. Harbor, for example, provides user management and role-based access control on top of the standard registry API.

The CI/CD problem. Build pipelines push images to a registry as an artifact, and deployment pipelines pull them from that registry. The registry is the handoff point between build and deploy, and the tag is the identifier that connects the two.


a. Image naming

A Docker image name is composed of three parts, with the registry and namespace optional in the local context but required for a push.

PartExampleRequired for push
Registrydocker.io, ghcr.io, myregistry.com:5000Implicit for Docker Hub, explicit for others
Namespacelibrary, myusername, myorgRequired for Docker Hub, varies for others
Repositorynginx, my-appRequired
Taglatest, v1.0.0Defaults to latest if omitted

The full form is registry/namespace/repository:tag. Docker Hub uses docker.io as the registry and defaults to library for official images, so nginx is the same as docker.io/library/nginx. A user’s image is docker.io/myusername/my-app:v1.0.

A local image that has never been tagged for a registry has only a repository name and a tag:

docker image ls
# REPOSITORY   TAG       IMAGE ID
# my-app       latest    abc123

This image cannot be pushed because Docker does not know where to push it. The fix is to tag it with the registry and namespace.


b. Authenticating to Docker Hub

Authentication to Docker Hub uses docker login. The command prompts for a username and password (or an access token) and stores the credentials in the configured credential store.

docker login

Docker Hub supports a web-based sign-in flow by default, which opens a browser for authentication. If --username is specified, the command prompts for a password or token.

docker login --username myusername

After a successful login, the output is:

Login Succeeded

The credentials are stored in the Docker configuration directory. With Docker Desktop, they are stored in the operating system’s native keychain. Without Docker Desktop, Docker uses a credential helper if one is configured, or stores the credentials in ~/.docker/config.json in base64-encoded form.

To log out and remove stored credentials:

docker logout

c. Tagging an image

Tagging assigns a new name to an existing image. The docker tag command takes a source image and a target name.

docker tag my-app myusername/my-app:v1.0

The source can be an image ID, a repository name with a tag, or a digest. The target is the fully qualified name for the registry.

A single image can have multiple tags:

docker tag my-app myusername/my-app:v1.0
docker tag my-app myusername/my-app:latest
docker tag my-app myusername/my-app:stable

Each tag is a separate name pointing to the same image ID. Pushing one tag does not push the others unless --all-tags is used.

docker push --all-tags myusername/my-app

The --all-tags flag pushes every tag for the specified repository.


d. Pushing to Docker Hub

After tagging, the push uploads the image to Docker Hub.

docker push myusername/my-app:v1.0

The output shows the layers being pushed and a digest at the end:

The push refers to repository [docker.io/myusername/my-app]
fbb8711b1824: Pushed
b686d86b3388: Pushed
...
v1.0: digest: sha256:... size: ...

The digest is the content address of the manifest. It can be used to pull the exact image even if the tag is later reassigned.

If the push fails with denied: requested access to the resource is denied, the common causes are: the repository does not exist on Docker Hub, the image is tagged with the wrong namespace, or the credentials are not for the account that owns the namespace.

The fix is to create the repository on Docker Hub, verify the tag matches the namespace, and ensure docker login was successful:

docker logout
docker login -u myusername
docker tag my-app myusername/my-app:v1.0
docker push myusername/my-app:v1.0

If the image or tag does not exist locally, the error is tag does not exist or An image does not exist locally with the tag. Running docker images shows the available images and tags, and the push should use a name that appears in the list.


e. Pushing to a private registry

A private registry is addressed by its hostname and optional port. The tag must include the registry address.

docker tag my-app registry.example.com:5000/my-app:v1.0
docker push registry.example.com:5000/my-app:v1.0

For private registries that require authentication, log in first:

docker login registry.example.com:5000

If the registry uses a self-signed TLS certificate, Docker must be configured to trust the registry’s CA. The CA certificate is placed in /etc/docker/certs.d/registry.example.com:5000/ca.crt on Linux, or the registry is added to the insecure registries list if TLS is not used.

For Kubernetes clusters, containerd must be configured to pull from the private registry. The registries.yaml file specifies the registry endpoints, authentication credentials, and TLS settings.

A private registry can also be run locally for development. The registry image provides a minimal registry:

docker run -d -p 5000:5000 --name registry registry:2
docker tag my-app localhost:5000/my-app
docker push localhost:5000/my-app

This registry stores images on the local filesystem and is accessible only from the local machine or from a container that can reach port 5000.


f. Harbor and enterprise registries

Harbor is an open-source registry that extends the standard Docker Distribution with enterprise features: user management, role-based access control, image replication, vulnerability scanning, and auditing.

Harbor is deployed as a set of containers and is configured with a harbor.cfg file. The key settings are the hostname, the protocol (HTTP or HTTPS), and the SSL certificate paths.

# harbor.cfg
hostname = harbor.example.com
ui_url_protocol = https
ssl_cert = /root/cert/harbor.example.com.crt
ssl_cert_key = /root/cert/harbor.example.com.key

After configuration, ./install.sh deploys Harbor, and the web UI is available at the configured hostname. The default credentials are admin / Harbor12345, which should be changed immediately.

Images are pushed to Harbor the same way as any registry:

docker login harbor.example.com
docker tag my-app harbor.example.com/myproject/my-app:v1.0
docker push harbor.example.com/myproject/my-app:v1.0

The namespace in the tag is the Harbor project, which controls who can access the repository.


Complete Example Session

# ============================================
# PART 1: BUILD A LOCAL IMAGE
# ============================================
cat > Dockerfile << 'EOF'
FROM alpine:3.19
CMD ["echo", "hello"]
EOF
docker build -t my-app .
# ============================================
# PART 2: TAG FOR DOCKER HUB
# ============================================
docker tag my-app myusername/my-app:v1.0
docker tag my-app myusername/my-app:latest
# ============================================
# PART 3: LOGIN TO DOCKER HUB
# ============================================
docker login --username myusername
# Enter password or token
# Login Succeeded
# ============================================
# PART 4: PUSH TO DOCKER HUB
# ============================================
docker push myusername/my-app:v1.0
# The push refers to repository [docker.io/myusername/my-app]
# ...digest: sha256:...
# ============================================
# PART 5: PUSH ALL TAGS
# ============================================
docker push --all-tags myusername/my-app
# ============================================
# PART 6: PULL FROM DOCKER HUB ON ANOTHER MACHINE
# ============================================
docker pull myusername/my-app:v1.0
# ============================================
# PART 7: RUN A LOCAL REGISTRY
# ============================================
docker run -d -p 5000:5000 --name registry registry:2
# ============================================
# PART 8: TAG AND PUSH TO LOCAL REGISTRY
# ============================================
docker tag my-app localhost:5000/my-app:v1.0
docker push localhost:5000/my-app:v1.0
# ============================================
# PART 9: LOGIN TO PRIVATE REGISTRY
# ============================================
docker login registry.example.com:5000
# Enter credentials
# ============================================
# PART 10: TAG AND PUSH TO PRIVATE REGISTRY
# ============================================
docker tag my-app registry.example.com:5000/my-app:v1.0
docker push registry.example.com:5000/my-app:v1.0

These ten parts cover building a local image, tagging for Docker Hub, logging in, pushing, pushing all tags, pulling on another machine, running a local registry, pushing to it, logging in to a private registry, and pushing to it.


Quick Reference

Image Name Parts

PartRequired for pushExample
RegistryYes (implicit for Docker Hub)docker.io, ghcr.io, registry:5000
NamespaceYes for Docker Hubmyusername, myorg
RepositoryYesmy-app
TagDefaults to latestv1.0

Login

CommandPurpose
docker loginAuthenticate to Docker Hub
docker login registry:portAuthenticate to private registry
docker login -u userSpecify username
docker logoutRemove stored credentials

Tag and Push

CommandPurpose
docker tag source targetAssign a new name
docker push name:tagPush a single tag
docker push --all-tags namePush all tags
docker pull name:tagPull an image

Common Errors

ErrorCauseFix
denied: requested accessWrong namespace or not logged inCheck tag, log in
tag does not existImage or tag not localCheck docker images
no basic auth credentialsNot logged in to private registrydocker login registry
x509: certificate signed by unknown authoritySelf-signed cert not trustedAdd CA to /etc/docker/certs.d/

Best Practices

✅ Do This:

docker tag my-app myusername/my-app:v1.0        # Full registry path
docker tag my-app myusername/my-app:1.2.3       # Specific version tag
docker login --username myusername              # Log in before pushing
docker push --all-tags myusername/my-app        # Push every tag
# /etc/docker/certs.d/registry:5000/ca.crt      # Trust private CA

❌ Don’t Do This:

docker push my-app                              # ❌ No registry component
docker tag my-app myusername/my-app:latest      # ❌ Unpredictable for production
docker push registry.example.com:5000/app       # ❌ Without login
# Plaintext credentials in config.json          # ❌ Use a credential helper

Common Pitfalls

PitfallWhy It HappensFix
Push deniedWrong namespace or not logged inCheck tag and login
Tag not foundImage not tagged locallyRun docker images
Slow pushLarge layersReduce layers, use .dockerignore
Private registry pull failsNot authenticated in containerdConfigure registries.yaml
Certificate errorSelf-signed cert not trustedAdd CA certificate
Rate limitDocker Hub unauthenticated pull limitLog in to increase limit

Real-World Examples

1. Tag for Docker Hub

docker tag my-app myusername/my-app:v1.0

2. Push to Docker Hub

docker push myusername/my-app:v1.0

3. Push All Tags

docker push --all-tags myusername/my-app

4. Local Registry

docker run -d -p 5000:5000 registry:2

5. Tag for Local Registry

docker tag my-app localhost:5000/my-app

6. Push to Local Registry

docker push localhost:5000/my-app

7. Login to Private Registry

docker login registry.example.com

8. Tag for Private Registry

docker tag my-app registry.example.com/myproject/my-app:v1.0

9. Push to Private Registry

docker push registry.example.com/myproject/my-app:v1.0

10. CI/CD Push

- run: docker build -t $REGISTRY/$IMAGE:$TAG .
- run: docker push $REGISTRY/$IMAGE:$TAG

Visual

Image Naming

┌──────────────────────────────────────────────────────────────┐
│  registry/namespace/repository:tag                           │
│                                                              │
│  docker.io/myusername/my-app:v1.0                            │
│  │         │          │       │                              │
│  │         │          │       └── tag                        │
│  │         │          └── repository                         │
│  │         └── namespace (Docker Hub username)               │
│  └── registry (implicit for Docker Hub)                      │
│                                                              │
│  registry.example.com:5000/myproject/my-app:v1.0             │
│  └── explicit registry with port                             │
└──────────────────────────────────────────────────────────────┘

Push Flow

┌──────────────────────────────────────────────────────────────┐
│  1. docker login                                             │
│     └── Store credentials                                    │
│                                                              │
│  2. docker tag my-app myusername/my-app:v1.0                 │
│     └── Assign fully qualified name                          │
│                                                              │
│  3. docker push myusername/my-app:v1.0                       │
│     └── Upload layers and manifest to registry               │
│                                                              │
│  4. Registry stores blobs by digest                          │
│     └── Manifest references config and layers                │
│                                                              │
│  5. Other machines: docker pull myusername/my-app:v1.0       │
└──────────────────────────────────────────────────────────────┘

Docker Hub vs Private Registry

┌──────────────────────────────────────────────────────────────┐
│  DOCKER HUB                        PRIVATE REGISTRY          │
│  ┌────────────────────────────┐    ┌──────────────────────┐  │
│  │  Default registry          │    │  Self-hosted         │  │
│  │  Public or private repos   │    │  Access controlled   │  │
│  │  Rate limits for free      │    │  No external limits  │  │
│  │  Simple auth               │    │  LDAP, OIDC, etc.    │  │
│  └────────────────────────────┘    └──────────────────────┘  │
│                                                              │
│  docker.io/myuser/myapp            registry.example.com/app  │
└──────────────────────────────────────────────────────────────┘

Credential Storage

┌──────────────────────────────────────────────────────────────┐
│  docker login                                                │
│       │                                                      │
│       ▼                                                      │
│  Credential store (if configured)                            │
│  ├── macOS: osxkeychain                                      │
│  ├── Windows: wincred                                        │
│  └── Linux: pass or secretservice                            │
│                                                              │
│  Otherwise:                                                  │
│  └── ~/.docker/config.json (base64-encoded)                  │
│                                                              │
│  Credentials are used by push and pull automatically.        │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Image nameregistry/namespace/repository:tag
Docker Hub registrydocker.io (implicit)
Docker Hub namespaceUsername or organization
TagDefaults to latest
Logindocker login
Credential storeKeychain, wincred, pass, or config.json
Tag commanddocker tag source target
Push commanddocker push name:tag
Push all tagsdocker push --all-tags name
Private registryregistry.example.com:port
Private registry authdocker login registry:port
Self-signed cert/etc/docker/certs.d/registry:port/ca.crt
HarborEnterprise registry with RBAC and scanning

Key takeaways:

  • An image name determines where it is pushed. The name has three parts: registry, namespace, and repository, with an optional tag. Docker Hub uses docker.io and the user’s namespace.
  • docker login stores credentials for push and pull. Docker Desktop stores them in the OS keychain. Without Docker Desktop, a credential helper or the config file is used.
  • docker tag assigns the fully qualified name. The source can be an image ID or a local name. The target must include the registry and namespace for a push.
  • docker push uploads the image to the registry. The output shows the layers and the manifest digest. --all-tags pushes every tag for the repository.
  • denied: requested access means the namespace or credentials are wrong. The repository must exist on Docker Hub, the tag must match the namespace, and the login must be for the account that owns the namespace.
  • Private registries are addressed by hostname and port. Login uses docker login registry:port. Self-signed certificates require adding the CA to /etc/docker/certs.d/.
  • Harbor adds enterprise features to the standard registry. It provides user management, role-based access control, image replication, and vulnerability scanning.

Remember: Pushing an image is the step that makes it available to other machines. The image name is the routing instruction: the registry determines where it goes, the namespace determines who owns it, and the repository and tag identify the specific version. Docker Hub is the default public registry, and its namespace is the user’s account. Private registries are addressed by hostname and port, and they require authentication. The workflow is always the same: log in, tag the image with the fully qualified name, and push. The most common failure is a mismatched namespace or a missing login, which produces the denied error. Understanding the naming convention and the authentication flow is what makes the push reliable.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!