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]
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
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
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
- Go to your GitLab project → Settings → CI/CD → Variables
- 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
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
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
8. Troubleshooting Tips
-
Login errors? Check that
DOCKER_USERNAME
andDOCKER_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)