Every time we run docker pull nginx or docker pull postgres, the image is actually pulled from Docker Hub, Docker's built-in public registry that serves as the default storage and distribution center for images. Docker Hub is not just a place to pull other people's images; as soon as we start building our own images via docker build, the next logical step is to store those images somewhere so they can be reused on other servers, shared with teammates, or pulled automatically by a CI/CD pipeline. This chapter covers how to register and log in to Docker Hub, the mechanics of pushing and pulling images, the differences between public and private repositories, and image versioning strategies to ensure deployments remain traceable and safely reproducible.
19.1 Registration and Login
Docker Hub requires an account before we can store our own images there, although pulling public images like nginx or alpine can still be done without logging in. This section covers how to create an account and log in securely from the Docker CLI.
19.1.1 Creating a Docker Hub Account
Open hub.docker.com in a browser, then register using an email or an existing GitHub/Google account. Docker Hub will prompt us to choose a Docker ID, which is a unique username that will serve as the namespace for all image repositories we own. This Docker ID is important to consider carefully from the start because it will always appear in front of the repository name, such as username/image-name, and cannot be easily changed later after many images and integrations depend on it.
New accounts automatically enroll in the free Docker Personal plan. This plan is sufficient for learning needs and small projects, although there are limits on the number of private repositories and pull rate limits compared to paid plans like Docker Pro or Team.
19.1.2 Logging in from the Docker CLI
Once the account is ready, log in from the terminal using the docker login command so that the Docker CLI on our computer has credentials to push to and pull from private repositories.
docker loginThis command prompts for a username and password, or opens a browser for the authentication process if run from the latest version of Docker Desktop. Once successful, the Docker CLI stores authentication tokens in a local configuration file (typically ~/.docker/config.json on Linux and macOS, or an equivalent location on Windows), so subsequent docker push and docker pull commands to Docker Hub do not require logging in again as long as the session remains valid.
To end a login session, for instance on a shared computer or a server used by multiple people, run docker logout so that locally stored credentials are removed.
docker logout19.1.3 Authenticating with Personal Access Tokens
According to official Docker documentation, access tokens provide a way to authenticate to Docker Hub without using the account password directly, and are required if the account has enabled two-factor authentication (2FA) or single sign-on (SSO), as CLI login using a standard password is no longer supported under those conditions. In practice, access tokens are also a safer choice for automated scenarios like CI/CD, because tokens can be created specifically for a single purpose, granted limited scope (such as read-only or read & write), and revoked at any time without changing the main account password.
Create an access token from the Account Settings > Personal access tokens page on Docker Hub, give it a clear name corresponding to its purpose (such as ci-pipeline-app), and copy the token since it will only be displayed once. Use that token in place of a password when logging in.
docker login --username usernameWhen prompted for a password, paste the generated access token instead of the Docker Hub account password. Store the token in a password manager or the secrets manager of the CI/CD platform being used, and never write it directly inside a Dockerfile, source code, or files committed to a Git repository, because anyone with access to the repository history could use that token to push or pull images on behalf of the account.
19.2 Pushing Images
Once an image is built locally, the next step is sending it to Docker Hub via docker push so it can be pulled from other machines. Before a push can succeed, the image must be named according to the conventions recognized by Docker Hub.
19.2.1 Repository Naming Conventions
The full name of an image follows the format [HOST[:PORT]/]PATH[:TAG]. If the HOST part is omitted, the Docker CLI assumes the image comes from docker.io, which is Docker Hub. For Docker Hub itself, the PATH portion takes the form NAMESPACE/REPOSITORY, where NAMESPACE is the account's Docker ID or organization name, and REPOSITORY is the custom image name we define.
Because of this, an image built locally with an arbitrary name like my-app needs to be retagged using docker tag so that its name contains our Docker Hub account namespace before it can be pushed.
docker tag my-app username/my-app:1.0The above command does not re-copy the entire contents of the image; docker tag merely adds a new name reference pointing to the same image ID, making the process nearly instantaneous regardless of image size. If the TAG part is omitted entirely, Docker automatically uses latest as the default tag, a habit to be cautious of because latest does not guarantee the content is actually the newest version.
19.2.2 Executing a Push
After the image has a name containing our namespace, run docker push specifying the full name along with its tag.
docker push username/my-app:1.0Docker uploads the image layer by layer. Layers that already exist on Docker Hub, for example because they originate from the same public base image like node:20-alpine, are automatically skipped and not re-uploaded, making subsequent pushes of images sharing a similar base image much faster than the initial push.
If a push fails with the message denied: requested access to the resource is denied, there are two common causes in practice: we have not logged in via docker login, or the namespace in the image name does not match the currently logged-in account. Run docker login again and ensure the image name explicitly starts with the Docker ID of the logged-in account, rather than an unassigned name like my-app alone.
If the target repository does not exist yet, Docker Hub automatically creates a new repository upon the first successful push, with visibility following the namespace's default repository privacy setting, which can be configured to public or private via the Settings > Default privacy page on the Docker Hub dashboard. Developers who want to ensure a specific repository's visibility from the outset, without relying on default namespace settings, should manually create the repository from the Docker Hub dashboard and set its visibility before running the first push. The visibility of an existing repository can still be modified at any time through its settings page, changing from public to private or vice versa.
19.3 Pulling Images
Pulling images from Docker Hub is one of the most frequent daily operations, whether for base images inside a Dockerfile or running containers directly from pre-built images.
19.3.1 Pulling Images from a Repository
Use docker pull followed by the image name and desired tag.
docker pull username/my-app:1.0For official images like nginx or postgres that lack a user namespace, Docker Hub actually stores them in a special namespace called library. Thus, docker pull nginx is equivalent to docker pull library/nginx, except the Docker CLI allows omitting the namespace for official images to keep commands concise.
If the pulled repository is private, ensure login has been performed with an account that has access to that repository before running docker pull, because without proper authentication Docker Hub will reject the request with an access denied error message, similar to pushing to a repository owned by another account.
19.3.2 Image Pull Rate Limits
Docker Hub enforces limits on the number of pulls within a six-hour window, calculated based on account type. According to official Docker documentation regarding usage and limits, unauthenticated users (not logged in) are limited to 100 pulls per 6 hours per IPv4 address or /64 IPv6 subnet, while logged-in Docker Personal accounts are limited to 200 pulls per 6 hours. Paid accounts such as Docker Pro, Team, and Business have no pull limits at all.
| Account Type | Pull Limit per 6 Hours |
|---|---|
| Unauthenticated (not logged in) | 100 per IPv4 address / IPv6 /64 subnet |
| Personal (logged in) | 200 |
| Pro, Team, Business (logged in) | Unlimited |
This limit frequently becomes a pitfall for Sysadmins/DevOps Engineers running multiple builds in CI/CD sharing the same IP address, such as shared runners used interchangeably across multiple projects. Because rate limits are computed per IP for unauthenticated users, all jobs under that IP are affected once the limit is hit, not just the job that happened to trigger the limit first. If a pull from Docker Hub fails with the message you have reached your pull rate limit and status code 429, the most immediate solution is to log in via docker login in the CI/CD process so limits are applied per account rather than per IP, or upgrade to a paid plan if pull volume is routinely high.
19.4 Public and Private Repositories
Docker Hub categorizes repository visibility into two types: public and private, which determines who can discover and pull images from that repository.
19.4.1 Managing Repository Visibility
Repositories with public visibility appear in Docker Hub search results and can be pulled by anyone without requiring login or special permissions. This pattern is suitable for open-source images or internal tools intended for public use. Conversely, private repositories do not show up in search results and can only be accessed by explicitly authorized accounts, making them suitable for internal company application images containing proprietary source code or sensitive configurations.
Change visibility through the repository settings page on the Docker Hub dashboard under Settings > Visibility. Note that changing a repository from private to public takes effect immediately upon saving; if an image in that repository previously stored sensitive data in any layer (such as an API key accidentally committed during a build), that data becomes accessible to anyone as soon as the repository becomes public, even if that layer is no longer referenced in the latest tag. Delete and rebuild images from scratch without sensitive data prior to changing visibility, rather than relying solely on tag deletion.
19.4.2 Collaborators and Private Repository Limits
For personal repositories (not owned by an organization), push and pull access to private repositories is managed through the collaborators feature, inviting other accounts explicitly via the Docker Hub dashboard. Organization repositories offer more granular access control through combinations of roles, teams, and organization access tokens, making them suitable for teams needing to specify who can push versus who can only pull, without sharing primary account credentials among all members.
The free Docker Personal plan limits the number of private repositories to just one, while the number of public repositories is unlimited. This limitation is an important consideration for developers planning to store multiple internal images on Docker Hub; if requirements exceed one private repository, options include upgrading to a Docker Pro/Team plan that provides unlimited private repositories, or setting up a self-hosted private registry to manage those images independently.
19.5 Image Versioning Strategies
Tagging images is not merely about naming; it is also a strategy to ensure every application version ever deployed can be traced and pulled again identically whenever needed, such as during production rollbacks to a previous version.
19.5.1 Semantic Versioning on Tags
The most common approach follows semantic versioning (MAJOR.MINOR.PATCH), where every image build deemed release-ready receives an explicit version tag instead of being left solely with a latest tag.
docker tag my-app username/my-app:1.2.0
docker push username/my-app:1.2.0In addition to complete version tags, many official images on Docker Hub also provide major/minor tags such as alpine:3.21, which automatically point to the latest patch version of the 3.21 series whenever the image is rebuilt by its maintainers. This pattern offers a balance between stability (staying within the 3.21 series) and receiving security patch updates automatically without manually updating tag references. Developers building custom images can adopt a similar pattern by pushing multiple tags simultaneously for the same commit.
docker tag my-app username/my-app:1.2.0
docker tag my-app username/my-app:1.2
docker tag my-app username/my-app:1
docker push username/my-app:1.2.0
docker push username/my-app:1.2
docker push username/my-app:1In practice, Sysadmins/DevOps Engineers managing CI/CD pipelines often add tags based on Git commit hashes or build numbers (such as username/my-app:git-a1b2c3d) alongside semantic version tags. This ensures every built image can be traced directly back to its source commit, which is particularly useful when debugging production incidents that require verifying the exact code version running.
19.5.2 Avoiding the latest Tag Pitfall
The latest tag is not an automatic "newest version" marker; it is simply the default tag used by Docker when no other tag is specified during push or pull operations. Consequently, latest points to whichever image was most recently pushed with that tag, which could be an older version if push order was inconsistent, or an unstable build if accidentally pushed from a development branch.
Using latest in production deployment files, such as inside compose.yaml or other orchestration manifests, creates a risk of non-reproducible deployments: running docker pull username/my-app:latest today and next week may yield different images despite zero configuration changes on our end. This complicates rollback procedures, as there is no reliable way to verify which version was running stably in production previously.
A safer practice is to always specify explicit version tags in production environments, keeping latest solely as an additional tag for development convenience or rapid experimentation.
services:
app:
image: username/my-app:1.2.0Explicit tags like the one above allow rolling back to a previous version simply by modifying the version number in the configuration file and re-executing the deployment, without needing to guess which image was actually running.

