DEV Community

Cover image for Building, Securing, and Deploying a Go App with GitLab CI/CD EP 4.2: Build and Push Docker Image to Docker Hub
Booranasak Kanthong
Booranasak Kanthong

Posted on

Building, Securing, and Deploying a Go App with GitLab CI/CD EP 4.2: Build and Push Docker Image to Docker Hub

Introduction

Welcome back to our CI/CD journey! Last time, you learned how to scan your Go code for bugs and vulnerabilities with SonarQube. In this episode, we’ll take your project to the next level by packaging your app as a Docker image and pushing it to Docker Hub.

By the end, you’ll know how to:

  • Build a Docker image of your Go app in your GitLab pipeline
  • Authenticate your pipeline with Docker Hub
  • Push your image to Docker Hub for safe storage and sharing

1. What is a Docker Image and Why Use a Registry?

A Docker image is like a recipe that describes how to run your app.
It bundles your code, dependencies, and configuration so your app runs the same everywhere.

But once you build an image, you need a place to store it. That’s where a container registry like Docker Hub comes in!
A registry lets you store, share, and deploy images—just like GitHub lets you store and share code.


Docker Image to Registry Flow

[GitLab Pipeline Triggered]
           |
           v
[Docker Build Job: Build App Image]
           |
           v
[Save Docker Image as .tar File]
           |
           v
[Push Job: Authenticate with Docker Hub]
           |
           v
[Load Docker Image from .tar File]
           |
           v
[Push Docker Image to Docker Hub]
           |
           v
[Image Available in Docker Hub]
Enter fullscreen mode Exit fullscreen mode

2. Setting Up Docker Hub

To push images to Docker Hub, you need:

  • A Docker Hub account
  • A repository created in Docker Hub under your username or organization

A. Create a Docker Hub Repository

1.) Go to https://hub.docker.com
2.) Sign in and click Create Repository

3.) Choose visibility (Public or Private), and name it something like booranasak-bank-api-golang

4.) Note the image name format:

   docker.io/YOUR_DOCKERHUB_USERNAME/booranasak-bank-api-golang:1.0.0
Enter fullscreen mode Exit fullscreen mode


3. Building Your Docker Image in GitLab CI/CD

Here’s the job for your .gitlab-ci.yml:

variables:
  IMAGE_NAME_DOCKERHUB: "booranasak-bank-api-golang"
  IMAGE_VERSION_TAG: "1.0.0"
  IMAGE_TAG_DOCKERHUB: "docker.io/YOUR_DOCKERHUB_USERNAME/$IMAGE_NAME_DOCKERHUB:$IMAGE_VERSION_TAG"
  IMAGE_TAR: todo-api.tar

build_image:
  stage: build
  image: docker:latest
  script:
    - docker build -t $IMAGE_TAG_DOCKERHUB .
    - docker save $IMAGE_TAG_DOCKERHUB -o $IMAGE_TAR
  artifacts:
    name: "docker-image-tar"
    paths:
      - $IMAGE_TAR
    expire_in: 1 hour
Enter fullscreen mode Exit fullscreen mode

4. Authenticating GitLab CI/CD with Docker Hub

You’ll use GitLab CI/CD variables to store your Docker Hub credentials.

A. Add Docker Hub Credentials to GitLab

  1. Go to your GitLab project → Settings → CI/CD → Variables
  2. Add the following CI/CD variables:
  • DOCKER_USERNAME: your Docker Hub username

* DOCKER_PASSWORD: your Docker Hub password or personal access token


5. Pushing the Image to Docker Hub

Here’s the job for pushing:

push_image_to_registry:
  stage: push
  image: docker:latest
  dependencies:
    - build_image
  before_script:
    - docker login -u "$DOCKER_USER" --password-stdin
  script:
    - docker load -i $IMAGE_TAR
    - docker push $IMAGE_TAG_DOCKERHUB
Enter fullscreen mode Exit fullscreen mode

6. Merge Scripts Together

Here’s how your complete .gitlab-ci.yml looks after replacing GAR with Docker Hub:

image: docker:latest

services:
  - docker:dind

variables:
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_TLS_CERTDIR: ""
  GO_VERSION: "1.24.3"
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
  IMAGE_USERNAME_DOCKERHUB: "mirrorheart"
  IMAGE_NAME_DOCKERHUB: "booranasak-bank-api-golang"
  IMAGE_VERSION_TAG: "1.0.0"
  IMAGE_TAG_DOCKERHUB: "$IMAGE_USERNAME_DOCKERHUB/$IMAGE_NAME_DOCKERHUB:$IMAGE_VERSION_TAG"  
  IMAGE_TAR: todo-api.tar
  GIT_DEPTH: "0"

stages:
  - lint
  - test
  - sast
  - build
  - push
  - sca_image
  - deploy
  - zap_scan

.go-job-template: &go-job-template
  image: debian:bullseye
  before_script:
    - apt update && apt install -y curl git tar gzip
    - curl -LO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
    - rm -rf /usr/local/go && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
    - export PATH="/usr/local/go/bin:$PATH"
    - go version

lint_golint:
  stage: lint
  <<: *go-job-template
  script:
    - export PATH="/usr/local/go/bin:$PATH"
    - go install golang.org/x/lint/golint@latest
    - export PATH="$PATH:$(go env GOPATH)/bin"
    - echo "Linting files:"
    - find . -name '*.go'
    - golint ./... | tee lint-report.txt
    - echo "--- Lint report preview ---"
    - cat lint-report.txt || echo "lint-report.txt is empty"
  allow_failure: true
  artifacts:
    name: "golint-report"
    paths:
      - lint-report.txt
    expire_in: 1 week

unit_test_and_coverage:
  stage: test
  <<: *go-job-template
  script:
    - export PATH="/usr/local/go/bin:$PATH"
    - go mod tidy
    - go test -v -cover ./...
    - go test -v -coverprofile=coverage.out ./...
  artifacts:
    paths:
      - coverage.out
    expire_in: 1 hour

sonarqube-check:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  dependencies:
    - unit_test_and_coverage
  script: 
    - sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}" -Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/*_test.go
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'

sonarqube-vulnerability-report:
  stage: sast
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  script:
    - 'curl -u "${SONAR_TOKEN}:" "${SONAR_HOST_URL}/api/issues/gitlab_sast_export?projectKey=booranasak-bank-golang-sast&branch=${CI_COMMIT_BRANCH}&pullRequest=${CI_MERGE_REQUEST_IID}" -o gl-sast-sonar-report.json'
  allow_failure: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == 'master'
    - if: $CI_COMMIT_BRANCH == 'main'
    - if: $CI_COMMIT_BRANCH == 'develop'
  artifacts:
    expire_in: 1 day
    reports:
      sast: gl-sast-sonar-report.json

build_container:
  stage: build
  image: docker:latest
  script:
    - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
    - docker build -t $IMAGE_TAG_DOCKERHUB .
    - docker save $IMAGE_TAG_DOCKERHUB -o $IMAGE_TAR
  artifacts:
    name: "docker-image-tar"
    paths:
      - $IMAGE_TAR
    expire_in: 1 hour

push_container_to_registry:
  stage: push
  image: docker:latest
  script:
    - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
    - docker load -i $IMAGE_TAR
    - docker push $IMAGE_TAG_DOCKERHUB
  dependencies:
    - build_container
Enter fullscreen mode Exit fullscreen mode


7. Checking Your Image in Docker Hub

Once your pipeline finishes:

  • Go to Docker Hub
  • Open your repository (e.g., booranasak-bank-api-golang)

  • You should see your image with the tag 1.0.0 listed


Test Pulling Your Image (Optional)

docker pull docker.io/YOUR_DOCKERHUB_USERNAME/booranasak-bank-api-golang:1.0.0
Enter fullscreen mode Exit fullscreen mode

8. Troubleshooting Tips

  • Login errors? Check that DOCKER_USERNAME and DOCKER_PASSWORD are correctly set
  • Pipeline fails at Docker commands? Ensure Docker-in-Docker (docker:dind) is enabled
  • Image not visible? Double-check the tag and Docker Hub repo name

9. What’s Next?

Awesome work! Your Go app is now packaged in a Docker image and stored in Docker Hub—ready to be deployed anywhere.

In the next episode, we’ll add Trivy to your pipeline to automatically scan your image for vulnerabilities.


Summary

  • You built and pushed Docker images via GitLab CI/CD
  • You configured Docker Hub authentication
  • Your app is now containerized and stored in a public/private registry

Ready for EP 5.2? Stay tuned — we're adding Trivy to scan Docker Hub images for vulnerabilities!

Top comments (0)