After optimizing your Docker images, the next step is sharing them with your team or deploying them to production. This comprehensive guide teaches you everything about Docker Hub, image tagging, pushing to registries, and deploying containers anywhere.
π― What You'll Learn: In this hands-on tutorial, you'll discover:
- Creating semantic version tags and tag management strategies
- Logging into Docker Hub and authentication
- Pushing images to public Docker registries
- Pulling images from Docker Hub on any machine
- Running containers with environment variables
- Mounting volumes for persistent data
- Monitoring container resource usage
- Troubleshooting port conflicts and common issues
- Production deployment best practices
π Why Docker Hub Matters
Docker Hub is the world's largest library and community for container images. It enables:
Benefit | Description |
---|---|
Team Collaboration | Share images across development teams |
CI/CD Integration | Automated builds and deployments |
Version Control | Tag and manage multiple versions |
Public Distribution | Share open-source projects globally |
Fast Deployment | Pull and deploy anywhere instantly |
Prerequisites
Before we begin, make sure you have:
- Completed Part 1: Docker Image Optimization
- A Docker Hub account (create at hub.docker.com)
- Docker installed and running
- An optimized image from Part 1 (
mywebapp:1.0.0
)
π·οΈ Step 1: Understanding Docker Tags
Tags are labels that identify specific versions of images. Think of them as Git tags or version numbers.
Tag Anatomy
username/repository:tag
β β β
β β ββ Version identifier
β ββββββββββββ Image name
βββββββββββββββββββββ Docker Hub username
Common Tagging Strategies
Strategy | Tag Examples | Use Case |
---|---|---|
Semantic Versioning | 1.0.0 , 1.0 , 1 | Production releases |
Environment Tags | dev , staging , prod | Deployment stages |
Stability Tags | latest , stable , beta | Release channels |
Git-based Tags | commit-abc123 , branch-main | CI/CD pipelines |
Create Multiple Tags
From Part 1, we have mywebapp:1.0.0
. Let's create additional tags:
docker tag mywebapp:1.0.0 mywebapp:1.0
docker tag mywebapp:1.0.0 mywebapp:latest
docker tag mywebapp:1.0.0 mywebapp:stable
What this does:
docker tag [source] [target]
: Creates a new tag pointing to same image- All tags reference the same image (same IMAGE ID)
- No additional disk space used
- Provides different ways to reference the image
Create Environment-Specific Tags
docker tag mywebapp:1.0.0 mywebapp:dev
docker tag mywebapp:1.0.0 mywebapp:staging
docker tag mywebapp:1.0.0 mywebapp:prod
View All Tags
docker images mywebapp
Expected Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
mywebapp 1.0 28912d412e73 57 seconds ago 52.5MB
mywebapp 1.0.0 28912d412e73 57 seconds ago 52.5MB
mywebapp latest 28912d412e73 57 seconds ago 52.5MB
mywebapp stable 28912d412e73 57 seconds ago 52.5MB
mywebapp dev 28912d412e73 58 seconds ago 52.5MB
mywebapp staging 28912d412e73 58 seconds ago 52.5MB
mywebapp prod 28912d412e73 58 seconds ago 52.5MB
Key observation: Notice all tags share the same IMAGE ID
(28912d412e73) - they're just different names for the same image!
π‘ Tag Best Practice: Always create a specific version tag (e.g., 1.0.0
) AND a latest
tag. This allows users to pin to specific versions or always get the newest release.
π Step 2: Login to Docker Hub
Before pushing images, authenticate with Docker Hub:
docker login
Interactive Authentication Process:
USING WEB-BASED LOGIN
i Info β To sign in with credentials on the command line, use 'docker login -u <username>'
Your one-time device confirmation code is: MVGB-ZNXJ
Press ENTER to open your browser or submit your device code here: https://login.docker.com/activate
Waiting for authentication in the browserβ¦
WARNING! Your credentials are stored unencrypted in '/home/centos9/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded
What happens:
- Docker generates a one-time code
- Opens browser for authentication
- You log in via Docker Hub website
- Credentials stored locally
- Ready to push/pull images
β οΈ Security Note: The warning about unencrypted credentials is normal for basic setup. For production, configure a credential helper to securely store Docker Hub tokens.
Alternative: Command-Line Login
docker login -u yourusername
Then enter your password when prompted.
π€ Step 3: Tag for Docker Hub
Docker Hub requires images to be tagged with your username:
docker tag mywebapp:1.0.0 owaisabbasi/mywebapp:1.0.0
docker tag mywebapp:1.0.0 owaisabbasi/mywebapp:latest
Format explained:
owaisabbasi
: Your Docker Hub usernamemywebapp
: Repository name on Docker Hub1.0.0
/latest
: Tag version
π Step 4: Push Images to Docker Hub
Push your optimized image to Docker Hub:
docker push owaisabbasi/mywebapp:1.0.0
Expected Output:
The push refers to repository [docker.io/owaisabbasi/mywebapp]
9c1c360a50dd: Pushed
28b29317ecc4: Pushed
f9985d3fc94d: Mounted from library/nginx
d208138be39d: Mounted from library/nginx
a2b76470e8f1: Mounted from library/nginx
917b2c97271e: Mounted from library/nginx
16ca725632e5: Mounted from library/nginx
7978a9c91f72: Mounted from library/nginx
b6ff0212304e: Mounted from library/nginx
418dccb7d85a: Mounted from library/nginx
1.0.0: digest: sha256:fba817dcf7cf01dda1b4de159b5d5079a5607ff544ec58aee65040e0c3d65ac8 size: 2403
Understanding the output:
- Each line represents a layer being pushed
- "Pushed" = New layer uploaded
- "Mounted from library/nginx" = Layer already exists (from base image)
- Only unique layers are uploaded (saves bandwidth!)
- Digest = Unique content hash of the image
Push Latest Tag
docker push owaisabbasi/mywebapp:latest
Expected Output:
The push refers to repository [docker.io/owaisabbasi/mywebapp]
9c1c360a50dd: Layer already exists
28b29317ecc4: Layer already exists
f9985d3fc94d: Layer already exists
d208138be39d: Layer already exists
a2b76470e8f1: Layer already exists
917b2c97271e: Layer already exists
16ca725632e5: Layer already exists
7978a9c91f72: Layer already exists
b6ff0212304e: Layer already exists
418dccb7d85a: Layer already exists
latest: digest: sha256:fba817dcf7cf01dda1b4de159b5d5079a5607ff544ec58aee65040e0c3d65ac8 size: 2403
Notice: All layers say "Layer already exists" - Docker is smart enough to not re-upload!
π Step 5: Verify on Docker Hub
Visit your Docker Hub repository to see your uploaded images:
https://hub.docker.com/repositories/yourusername
Replace yourusername
with your actual Docker Hub username.
You should see:
- Your repository name (
mywebapp
) - Tags (
1.0.0
,latest
) - Last pushed timestamp
- Public/Private visibility
- Image size
π Step 6: Search for Your Image
Use Docker search to find your published image:
docker search owaisabbasi/mywebapp
Expected Output:
NAME DESCRIPTION STARS OFFICIAL
owaisabbasi/mywebapp 0
milosmaljen/mywebapp 0
shadialborsan/mywebapp 0
devopsjourney1/mywebapp 2
Your image is now publicly discoverable!
π§Ή Step 7: Clean Local Images
Before pulling from Docker Hub, remove local images to simulate a fresh deployment:
docker rmi mywebapp:1.0 mywebapp:dev mywebapp:prod mywebapp:staging owaisabbasi/mywebapp:1.0.0
Expected Output:
Untagged: mywebapp:1.0
Untagged: mywebapp:dev
Untagged: mywebapp:prod
Untagged: mywebapp:staging
Untagged: owaisabbasi/mywebapp:1.0.0
Untagged: owaisabbasi/mywebapp@sha256:fba817dcf7cf01dda1b4de159b5d5079a5607ff544ec58aee65040e0c3d65ac8
Deleted: sha256:28912d412e7322d6a5660178fe819a6762b43cfacd4227c1fa32bec8b60462ff
Verify Removal
docker images | grep mywebapp
Should show no results (or only remaining tags).
π₯ Step 8: Pull Images from Docker Hub
Pull your images from Docker Hub to any machine:
docker pull owaisabbasi/mywebapp:latest
Expected Output:
latest: Pulling from owaisabbasi/mywebapp
9824c27679d3: Already exists
6bc572a340ec: Already exists
403e3f251637: Already exists
9adfbae99cb7: Already exists
7a8a46741e18: Already exists
c9ebe2ff2d2c: Already exists
a992fbc61ecc: Already exists
cb1ff4086f82: Already exists
6ee564430b1d: Already exists
4da2f98b6445: Already exists
Digest: sha256:fba817dcf7cf01dda1b4de159b5d5079a5607ff544ec58aee65040e0c3d65ac8
Status: Downloaded newer image for owaisabbasi/mywebapp:latest
docker.io/owaisabbasi/mywebapp:latest
Pull Specific Version
docker pull owaisabbasi/mywebapp:1.0.0
Output:
1.0.0: Pulling from owaisabbasi/mywebapp
Digest: sha256:fba817dcf7cf01dda1b4de159b5d5079a5607ff544ec58aee65040e0c3d65ac8
Status: Downloaded newer image for owaisabbasi/mywebapp:1.0.0
docker.io/owaisabbasi/mywebapp:1.0.0
Verify Downloaded Images
docker images owaisabbasi/mywebapp
Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
owaisabbasi/mywebapp 1.0.0 28912d412e73 25 minutes ago 52.5MB
owaisabbasi/mywebapp latest 28912d412e73 25 minutes ago 52.5MB
π Step 9: Run Container from Docker Hub Image
Deploy your container from the pulled image:
docker run -d -p 8080:80 --name production-webapp owaisabbasi/mywebapp:latest
Command breakdown:
run
: Create and start a container-d
: Detached mode (runs in background)-p 8080:80
: Map host port 8080 to container port 80--name production-webapp
: Give container a friendly nameowaisabbasi/mywebapp:latest
: Image to run
Expected Output:
48ba78f3b35b5c3bfef5b7bf83b912e3318d1c9c728134ed3902c3c008314ca8
This long string is the container ID.
Test the Application
curl http://localhost:8080
Expected Output:
<!DOCTYPE html>
<html>
<head>
<title>Docker Optimization Lab</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 600px; margin: 0 auto; }
h1 { color: #2c3e50; }
.info { background: #ecf0f1; padding: 20px; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Docker Optimization Lab</h1>
<div class="info">
<p>This is a sample web application running in a Docker container.</p>
<p>Image optimization techniques help reduce size and improve performance.</p>
</div>
</div>
</body>
</html>
Or use grep to verify specific content:
curl -s http://localhost:8080 | grep "Docker Optimization Lab"
Output:
<title>Docker Optimization Lab</title>
<h1>Welcome to Docker Optimization Lab</h1>
π Step 10: Monitor Container
Check Running Containers
docker ps
Output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
48ba78f3b35b owaisabbasi/mywebapp:latest "/docker-entrypoint.β¦" 26 seconds ago Up 25 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp production-webapp
View Container Logs
docker logs production-webapp
Expected Output:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/10/02 20:08:17 [notice] 1#1: using the "epoll" event method
2025/10/02 20:08:17 [notice] 1#1: nginx/1.29.1
2025/10/02 20:08:17 [notice] 1#1: start worker processes
2025/10/02 20:08:17 [notice] 1#1: start worker process 29
172.17.0.1 - - [02/Oct/2025:20:08:27 +0000] "GET / HTTP/1.1" 200 661 "-" "curl/7.76.1" "-"
Monitor Resource Usage
docker stats production-webapp --no-stream
Expected Output:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
48ba78f3b35b production-webapp 0.00% 4.613MiB / 7.501GiB 0.06% 6.03kB / 2.81kB 8.19kB / 12.3kB 5
Metrics explained:
- CPU %: Percentage of CPU being used (0% when idle)
- MEM USAGE: Current memory usage (4.6MB - very efficient!)
- NET I/O: Network traffic in/out
- BLOCK I/O: Disk read/write
- PIDS: Number of processes running
π§ Step 11: Advanced Container Operations
Run with Environment Variables
docker run -d -p 8081:80 \
--name webapp-custom \
-e NGINX_HOST=localhost \
-e NGINX_PORT=80 \
owaisabbasi/mywebapp:latest
What this does:
-e NGINX_HOST=localhost
: Sets environment variable- Multiple
-e
flags for multiple variables - Container can read these variables
Run with Volume Mounts
Create custom content and mount it:
mkdir custom-content
echo "<h1>Custom Content</h1>" > custom-content/custom.html
docker run -d -p 8082:80 \
--name webapp-volume \
-v $(pwd)/custom-content:/usr/share/nginx/html/custom \
owaisabbasi/mywebapp:latest
Volume mount explained:
-v source:destination
: Mount directory$(pwd)/custom-content
: Host directory (current directory + custom-content)/usr/share/nginx/html/custom
: Container directory- Changes on host reflect in container immediately!
Test Multiple Deployments
curl http://localhost:8080 # Original container
curl http://localhost:8081 # With environment variables
curl http://localhost:8082/custom/custom.html # With volume mount
Output for volume-mounted container:
<h1>Custom Content</h1>
π¨ Step 12: Troubleshooting Common Issues
Issue 1: Port Already in Use
Error:
Error response from daemon: driver failed programming external connectivity on endpoint webapp:
Bind for 0.0.0.0:8080 failed: port is already allocated
Solution: Find what's using the port
sudo netstat -tulnp | grep :8080
Or using ss:
sudo ss -tulnp | grep :8080
Or using lsof:
sudo lsof -i :8080
Fix options:
-
Stop conflicting service:
docker stop $(docker ps -q --filter publish=8080)
-
Use different port:
docker run -d -p 8090:80 --name webapp owaisabbasi/mywebapp:latest
Issue 2: Container Immediately Exits
Check container status:
docker ps -a
Check logs for errors:
docker logs container-name
Common causes:
- Application crashes
- Missing files or configuration
- Permission issues
Issue 3: Cannot Pull Image
Error:
Error response from daemon: pull access denied for owaisabbasi/mywebapp, repository does not exist or may require 'docker login'
Solutions:
- Verify repository name is correct
- Ensure you're logged in:
docker login
- Check image exists on Docker Hub
- Verify image is public (or you have access)
π§Ή Step 13: Cleanup
Stop All Running Containers
docker stop $(docker ps -q)
Output:
4477069043b3
f5bd59801779
48ba78f3b35b
Remove All Containers
docker rm $(docker ps -aq)
Remove All Images
docker rmi $(docker images -q)
System-Wide Cleanup
docker system prune -f
Expected Output:
Deleted build cache objects:
mnrjzgy5tjqy6u3krteagrrsq
8qmhcpgzcd400zvavzyz8zxqa
...
Total reclaimed space: 213.6MB
π― Production Best Practices
1. Use Specific Tags in Production
# Bad - unpredictable
image: owaisabbasi/mywebapp:latest
# Good - reproducible
image: owaisabbasi/mywebapp:1.0.0
2. Never Use latest
in Production
The latest
tag can change unexpectedly, causing:
- Inconsistent deployments
- Difficult rollbacks
- Debugging nightmares
3. Implement Health Checks
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
4. Use Multi-Stage Builds
- Smaller production images
- No build tools in runtime
- Better security
5. Set Resource Limits
docker run -d \
--memory="512m" \
--cpus="0.5" \
mywebapp:1.0.0
6. Use Read-Only Root Filesystem
docker run -d --read-only \
-v /tmp \
mywebapp:1.0.0
7. Run as Non-Root User
RUN addgroup -g 1001 appgroup && \
adduser -S -u 1001 -G appgroup appuser
USER appuser
8. Scan Images for Vulnerabilities
docker scan owaisabbasi/mywebapp:1.0.0
9. Use Private Registries for Sensitive Apps
Consider:
- Docker Hub private repositories
- AWS ECR
- Google Container Registry
- Azure Container Registry
10. Implement Proper Logging
docker run -d \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
mywebapp:1.0.0
π Complete Command Reference
Command | Purpose |
---|---|
docker tag source target | Create new tag for image |
docker login | Authenticate with Docker Hub |
docker push username/image:tag | Upload image to registry |
docker pull username/image:tag | Download image from registry |
docker search keyword | Search Docker Hub |
docker run -d -p host:container image | Run detached with port mapping |
docker run -v host:container image | Run with volume mount |
docker run -e VAR=value image | Run with environment variable |
docker logs container | View container logs |
docker stats container | Monitor resource usage |
docker stop container | Stop running container |
docker rm container | Remove stopped container |
docker rmi image | Remove image |
docker system prune -f | Clean up unused resources |
π Key Takeaways
β Remember These Points
- Tags are aliases for specific image versions
- Docker Hub enables global image distribution
- docker push uploads images to registries
- docker pull downloads images from anywhere
- Environment variables customize container behavior
- Volume mounts provide persistent data storage
- Resource monitoring tracks container performance
- Port mapping exposes container services to the host
- Specific version tags ensure reproducible deployments
- Cleanup regularly to manage disk space
π Further Reading
Official Resources
π Congratulations! You've mastered Docker image distribution! You can now tag images, push to Docker Hub, pull from registries, and deploy containers with confidence. Combined with Part 1's optimization techniques, you have everything needed for production-ready Docker deployments.
π¬ Discussion
Share your deployment experiences:
- What's your Docker Hub repository strategy?
- How do you manage image versions in production?
- Have you encountered port conflicts? How did you solve them?
- What's your favorite Docker deployment workflow?
Connect with me: