DEV Community

Michelle
Michelle

Posted on

30-Day Cloud & DevOps Challenge: Day 11 — Pushing Docker Images to Registry

Yesterday, my Jenkins pipeline could build Docker images for backend and frontend.

But those images only existed on my local machine. They weren't accessible anywhere else, not on another developer's computer, not on a cloud server, not anywhere.

Today, I fixed that. I configured Jenkins to push Docker images to Docker Hub a public registry where anyone can pull my images.

Now my images are available everywhere.


First: What is a Docker Registry?

The Analogy

Concept Analogy
Docker image A shipping container with your app
Docker registry A shipping port where containers are stored
Docker Hub The largest public shipping port
docker pull Bringing a container FROM the port
docker push Sending a container TO the port

Why Push to a Registry?

Without Registry With Registry
Images only on your laptop Images available anywhere
Can't share with team Anyone can pull the image
Need to rebuild on every server Pull once, run anywhere
No versioning Tag versions (v1.0, latest)

Step 1: Create Docker Hub Account

If you don't have one:

  1. Go to hub.docker.com
  2. Sign up for a free account
  3. Remember your username

Step 2: Add Docker Hub Credentials to Jenkins

Why Credentials?

Jenkins needs to log in to Docker Hub before it can push images.

Adding Credentials

  1. Open Jenkins: http://localhost:8081
  2. Manage Jenkins -> Credentials
  3. System -> Global credentials -> Add Credentials

Fill out the form:

Field Value
Kind Username with password
Username Your Docker Hub username
Password Your Docker Hub password
ID docker-hub-credentials
Description Docker Hub login for pushing images
  1. Click "Create"

Step 3: Update Jenkinsfile with Push Stage

The Complete Jenkinsfile with Push

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = 'docker.io'
        DOCKER_USERNAME = 'your-dockerhub-username'  // Replace this!
        BACKEND_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_USERNAME}/myapp-backend:latest"
        FRONTEND_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_USERNAME}/myapp-frontend:latest"
    }

    stages {
        stage('Checkout') {
            steps {
                echo 'Cloning repository...'
                checkout scm
            }
        }

        stage('Backend Dependencies') {
            steps {
                dir('backend') {
                    sh 'npm install'
                }
            }
        }

        stage('Frontend Build') {
            steps {
                dir('frontend') {
                    sh 'npm install'
                    sh 'npm run build'
                }
            }
        }

        stage('Docker Build') {
            steps {
                echo 'Building Docker images...'
                sh 'docker build -t ${BACKEND_IMAGE} ./backend'
                sh 'docker build -t ${FRONTEND_IMAGE} ./frontend'
            }
        }

        stage('Docker Push') {
            steps {
                echo 'Pushing to Docker Hub...'
                withCredentials([usernamePassword(
                    credentialsId: 'docker-hub-credentials',
                    usernameVariable: 'DOCKER_USER',
                    passwordVariable: 'DOCKER_PASS'
                )]) {
                    sh 'echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin'
                    sh 'docker push ${BACKEND_IMAGE}'
                    sh 'docker push ${FRONTEND_IMAGE}'
                }
            }
        }

        stage('Success') {
            steps {
                echo 'Pipeline completed successfully!'
                echo "Images pushed to:"
                echo "  - ${BACKEND_IMAGE}"
                echo "  - ${FRONTEND_IMAGE}"
            }
        }
    }

    post {
        failure {
            echo 'Pipeline failed! Check the logs.'
        }
        success {
            echo 'Images published to Docker Hub!'
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Understanding New Parts

Part What it does
environment { DOCKER_USERNAME } Stores your Docker Hub username
BACKEND_IMAGE = "docker.io/username/image" Full image name including registry
withCredentials() Safely uses credentials without exposing passwords
docker login Authenticates with Docker Hub
docker push Uploads the image to the registry

Step 4: Replace the Username

Important: Change your-dockerhub-username to YOUR actual Docker Hub username!

DOCKER_USERNAME = 'johndoe'  // Use your actual username
Enter fullscreen mode Exit fullscreen mode

Understanding Credential Security

What NOT to do (Hardcoding passwords)

// DON'T DO THIS!
sh 'docker login -u johndoe -p MyRealPassword123'
Enter fullscreen mode Exit fullscreen mode

Why not? Anyone who sees your Jenkinsfile can steal your password!

What TO do (Using Jenkins credentials)

withCredentials([usernamePassword(
    credentialsId: 'docker-hub-credentials',
    usernameVariable: 'DOCKER_USER',
    passwordVariable: 'DOCKER_PASS'
)]) {
    sh 'echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin'
}
Enter fullscreen mode Exit fullscreen mode

Why this is safe:

  • Password never appears in logs
  • Password not stored in Jenkinsfile
  • Jenkins encrypts credentials
  • Only authorized users can access

Step 5: Commit and Push

git add Jenkinsfile
git commit -m "ci: add Docker Hub push stage

- Tag images with Docker Hub registry
- Add credentials for Docker login
- Push backend and frontend to Docker Hub"
git push origin main
Enter fullscreen mode Exit fullscreen mode

Step 6: Run the Pipeline

  1. Go to Jenkins -> microservices-ci
  2. Click "Build Now"
  3. Watch the console output

Console Output

[Pipeline] stage
[Pipeline] { (Docker Push)
[Pipeline] echo
Pushing to Docker Hub...
[Pipeline] withCredentials
[Pipeline] {
[Pipeline] sh
+ echo ****
+ docker login -u johndoe --password-stdin
Login Succeeded

[Pipeline] sh
+ docker push docker.io/johndoe/myapp-backend:latest
The push refers to repository [docker.io/mkangeth/myapp-backend]
latest: digest: sha256:abc123... size: 2420

[Pipeline] sh
+ docker push docker.io/johndoe/myapp-frontend:latest
The push refers to repository [docker.io/johndoe/myapp-frontend]
latest: digest: sha256:def456... size: 1024

[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Success)
[Pipeline] echo
Pipeline completed successfully!
Images pushed to:
  - docker.io/johndoe/myapp-backend:latest
  - docker.io/johndoe/myapp-frontend:latest
Finished: SUCCESS
Enter fullscreen mode Exit fullscreen mode

Step 7: Verify on Docker Hub

  1. Go to https://hub.docker.com/u/your-username
  2. You should see:
    • myapp-backend
    • myapp-frontend

Check Repository Details

Click on myapp-backend:

  • Tags: latest
  • Size: ~135MB
  • Pull command: docker pull yourusername/myapp-backend:latest
  • Last pushed: Just now

Step 8: Test Pulling from Any Computer

On any machine with Docker:

# Pull your images
docker pull yourusername/myapp-backend:latest
docker pull yourusername/myapp-frontend:latest

# Run them
docker run -d -p 3001:5000 yourusername/myapp-backend:latest
docker run -d -p 8080:80 yourusername/myapp-frontend:latest

# Test
curl http://localhost:3001/health
# Open browser to http://localhost:8080
Enter fullscreen mode Exit fullscreen mode

It works anywhere in the world!


Before vs After

Aspect Before (Day 10) After (Day 11)
Images location Only on local machine Docker Hub (worldwide)
Sharing Send files manually docker pull
Team access Send images via USB/email Anyone can pull
Deployment Copy images to server Pull from registry
Version tracking Manual Tags in registry

Complete CI/CD Pipeline Now

+-----------------------------------------------------------------------------------+
|                         YOUR COMPLETE CI/CD PIPELINE                              |
+-----------------------------------------------------------------------------------+
|                                                                                   |
|   git push -> Jenkins -> Build -> Docker -> Push -> Available                     |
|                    |          |         |        |                               |
|                    v          v         v        v                               |
|                  Pulls     Installs   Creates  Uploads                           |
|                  code      deps       images   to Hub                            |
|                                                                                   |
+-----------------------------------------------------------------------------------+

                              |
                              v
                    +-----------------+
                    |   DOCKER HUB    |
                    |  (Your Images)  |
                    +-----------------+
                              |
                              v
              +---------------+---------------+
              v               v               v
         Your Laptop     Cloud Server    Team Member
         (docker pull)   (docker pull)   (docker pull)
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Images need a home = Docker Registry (Docker Hub is the default)
  • Credentials protect secrets = Never hardcode passwords in Jenkinsfile
  • Tag images properly = Include registry and username in the tag
  • Push once, pull anywhere = Images become globally available
  • Latest is fine, versions are better = Consider using build numbers as tags

Resources


Let's Connect!

Have you pushed images to Docker Hub before? What registry do you use (Docker Hub, AWS ECR, GCR, others)?

Drop a comment or connect on LinkedIn. Let's learn together!


Top comments (0)