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.
| Part | Example | Required for push |
|---|---|---|
| Registry | docker.io, ghcr.io, myregistry.com:5000 | Implicit for Docker Hub, explicit for others |
| Namespace | library, myusername, myorg | Required for Docker Hub, varies for others |
| Repository | nginx, my-app | Required |
| Tag | latest, v1.0.0 | Defaults 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
| Part | Required for push | Example |
|---|---|---|
| Registry | Yes (implicit for Docker Hub) | docker.io, ghcr.io, registry:5000 |
| Namespace | Yes for Docker Hub | myusername, myorg |
| Repository | Yes | my-app |
| Tag | Defaults to latest | v1.0 |
Login
| Command | Purpose |
|---|---|
docker login | Authenticate to Docker Hub |
docker login registry:port | Authenticate to private registry |
docker login -u user | Specify username |
docker logout | Remove stored credentials |
Tag and Push
| Command | Purpose |
|---|---|
docker tag source target | Assign a new name |
docker push name:tag | Push a single tag |
docker push --all-tags name | Push all tags |
docker pull name:tag | Pull an image |
Common Errors
| Error | Cause | Fix |
|---|---|---|
denied: requested access | Wrong namespace or not logged in | Check tag, log in |
tag does not exist | Image or tag not local | Check docker images |
no basic auth credentials | Not logged in to private registry | docker login registry |
x509: certificate signed by unknown authority | Self-signed cert not trusted | Add 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Push denied | Wrong namespace or not logged in | Check tag and login |
| Tag not found | Image not tagged locally | Run docker images |
| Slow push | Large layers | Reduce layers, use .dockerignore |
| Private registry pull fails | Not authenticated in containerd | Configure registries.yaml |
| Certificate error | Self-signed cert not trusted | Add CA certificate |
| Rate limit | Docker Hub unauthenticated pull limit | Log 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
| Item | Value |
|---|---|
| Image name | registry/namespace/repository:tag |
| Docker Hub registry | docker.io (implicit) |
| Docker Hub namespace | Username or organization |
| Tag | Defaults to latest |
| Login | docker login |
| Credential store | Keychain, wincred, pass, or config.json |
| Tag command | docker tag source target |
| Push command | docker push name:tag |
| Push all tags | docker push --all-tags name |
| Private registry | registry.example.com:port |
| Private registry auth | docker login registry:port |
| Self-signed cert | /etc/docker/certs.d/registry:port/ca.crt |
| Harbor | Enterprise 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.ioand the user’s namespace. docker loginstores 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 tagassigns 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 pushuploads the image to the registry. The output shows the layers and the manifest digest.--all-tagspushes every tag for the repository.denied: requested accessmeans 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!