Skip to main content

GitLab CI Pipeline: Compile and Deploy a Gin-based Go Web-app via Multistage Dockerfile

1533 words·
GitLab GitLab CI CI Pipeline Go Golang Gin Multistage Dockerfile Docker Webserver Unprivileged Container
Table of Contents
GitLab-CI-Go-Web-Application - This article is part of a series.
Part 1: This Article
GitHub Repository Available

Overview
#

My GitLab Setup
#

In this tutorial I’m using the following setup based on Ubuntu 22.04 servers, GitLab is dockerized:

192.168.70.4 # GitLab
192.168.70.5 # GitLab Runner
192.168.70.6 # Deployment Server with Docker platform installed

I’m using the same setup for the deployment via SSH as described in my blog post: GitLab CI Pipeline - Deploy Static Website via Nginx Container

The main requirements for the deployment via SSH key are the following:

  • Create a SSH key pair on the server where GitLab is deployed

  • Copy the public SSH key to the authorized keys file of the Deployment server

  • SSH into the deployment servers to add the fingerprint

  • Add the private SSH key as variable to the GitLab Repository CI/CD settings

  • On the deployment server, a user named “gitlab-deployment”, that is member of the Docker group, is used for the authentication of for the GitLab CI pipeline


Container Image Name
#

The build container image name in the GitLab CI pipeline follows this naming convention: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA

  • $CI_REGISTRY_IMAGE Points to the container registry for the project.

  • $CI_COMMIT_REF_SLUG Represents the branch or tag.

  • $CI_COMMIT_SHA Ensures every image is uniquely tied to a specific commit for traceability and reproducibility.


In my project named “go-webserver”, the final image name looks like this:

# Container image name
gitlab-registry.jklug.work/go/go-webserver/main:6e58374aca498ee7376812d0b3b4f1fbd6f8078d
  • Registry: gitlab-registry.jklug.work/go

  • Project: go-webserver

  • Branch: main

  • Commit: 6e58374aca498ee7376812d0b3b4f1fbd6f8078d


GitLab CI Pipleine
#

This GitLab repository is available on GitHub:
https://github.com/jueklu/gitlab-ci-deploy-go-webserver

The following GitLab CI pipeline uses Docker-in-Docker (DinD) for the build stage and the host-based Docker daemon for the deployment stage.

This pipeline deploys the “Go Webserver Example Project” from my previous post: “Golang / Go Programming Language”



GitLab Repository
#

File & Folder Structure
#

The file and folder structure of the GitLab repository looks like this:

GitLab-Repository
├── Dockerfile  # Dockerfile
├── .gitlab-ci.yml  # GitLab CI Pipeline manifest
├── golang-source-code  # Go webserver application
│   ├── go.mod  # Defines module, dependencies and Go version
│   ├── go.sum  # Tracks the checksum of dependencies for module integrity
│   ├── main.go  # Main application code
│   └── views
│       └── index.html  # Example HTML file
└── README.md

Pipeline Manifest
#

  • .gitlab-ci.yml
### Variables
variables:
  DEPLOY_IP: "192.168.70.6"  # Deployment server IP
  DEPLOY_USER: "gitlab-deployment"  # Deployment server SSH user
  DEPLOY_PORT_HOST: 8080  # Host port
  DEPLOY_PORT_CONT: 8000  # Container port
  CONTAINER_NAME: "go-webserver"  # Name of the deployed container

  # Define the image name, tagging it with the GitLab CI registry and the current commit SHA
  IMAGE_SHA: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA


### Stages
stages:
  - build
  - deploy


### Build Container Image
build_image:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  variables:
    DOCKER_TLS_CERTDIR: ""
  before_script:
    # Login to GitLab Container Registry using predefined CI/CD variables
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    # Build the Docker image from the specified Dockerfile in the Dockerfiles directory
    - docker build --pull -t $IMAGE_SHA -f Dockerfile .
    # Push the built Docker image to the GitLab Container Registry
    - docker push $IMAGE_SHA
  rules: 
    # Rule: Run this job only for the main branch and if the specified Dockerfile exists
    - if: $CI_COMMIT_BRANCH == "main"
      exists:
        - Dockerfile


### Deploy Container to Virtual Machine
deploy_container:
  stage: deploy
  image: alpine:latest
  needs:
    - build_image  # Run this job only if the 'build_image' job succeeds
  before_script:
    # Update the package index, install the OpenSSH client for SSH connections
    - apk update && apk add openssh-client
    # If the private SSH key file ($ID_RSA) exists, set secure permissions (read/write for the owner only)
    - if [ -f "$ID_RSA" ]; then chmod og= $ID_RSA; fi
  script:
    #  SSH into the deployment server, log in to the GitLab Container registry
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
     # SSH into the deployment server, pull the image from the registry
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker pull $IMAGE_SHA"
    # SSH into the deployment server, remove the existing container (if it exists)
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker container rm -f $CONTAINER_NAME || true"
    # SSH into the deployment server, run the new container
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker run -d -p $DEPLOY_PORT_HOST:$DEPLOY_PORT_CONT --restart=unless-stopped --name $CONTAINER_NAME $IMAGE_SHA"
  rules:
    # Rule: Run this job only for main branch
    - if: $CI_COMMIT_BRANCH == "main"
  • docker build --pull -t $IMAGE_SHA -f Dockerfile . The --pull option tells Docker to check for the latest version of the image defined in the Dockerfile

Dockerfile
#

  • Dockerfile
### Stage 1: Compile Binary

# Start from Golang Alpine base image / Label image as "builder"
FROM golang:1.21-alpine AS builder

# Create build directory
RUN mkdir /build

# Define working directory inside container
WORKDIR /build

# Copy Go module files
COPY \
  golang-source-code/go.mod \
  golang-source-code/go.sum \
  ./

# Downloads Dependencies
RUN go mod download

# Copy the remaining source files
COPY golang-source-code/main.go ./

# Compile Go file into an executable binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o binary-example-file .



### Stage 2: Final Container

# Start from Alpine base image
FROM alpine:latest

# Creates system user "appuser" / no PW
RUN adduser -S -D -H -h /app appuser

# Switch to "appuser"
USER appuser

# Copy "helloworld" binary from the "compile" stage to the "/app" directory in the container
COPY --from=builder /build/binary-example-file /app/

# Copy "views" directory from the build context (host) into "/app/views" in the container
COPY golang-source-code/views/ /app/views

# Define working directory
WORKDIR /app

# Expose port 8080 for the application
EXPOSE 8080

# Execute the Go binary when the container starts
ENTRYPOINT ["./binary-example-file"]

Notes:

  • In the first stage the COPY sections are seperated for “go.mod”, “go.sum” and for the “main.go” file, to create reusable Docker layers.

  • The multistage build in the Dockerfile saves about 550MB in the final container image.



Go Source Code
#

main.go
#

  • golang-source-code/main.go
// Declare the main package / required for any standalone executable Go program
package main

import (
        "github.com/gin-gonic/contrib/static"
        "github.com/gin-gonic/gin"
)

func main() {
        r := gin.Default()

        // Serve static files from the "./views" directory
        r.Use(static.Serve("/", static.LocalFile("./views", true)))

        // Start the server on port 8080
        r.Run("0.0.0.0:8080")
}

go.mod
#

  • golang-source-code/go.mod
module jueklu/some-go-project

go 1.21.8

require (
        github.com/bytedance/sonic v1.11.6 // indirect
        github.com/bytedance/sonic/loader v0.1.1 // indirect
        github.com/cloudwego/base64x v0.1.4 // indirect
        github.com/cloudwego/iasm v0.2.0 // indirect
        github.com/gabriel-vasile/mimetype v1.4.3 // indirect
        github.com/gin-contrib/sse v0.1.0 // indirect
        github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 // indirect
        github.com/gin-gonic/gin v1.10.0 // indirect
        github.com/go-playground/locales v0.14.1 // indirect
        github.com/go-playground/universal-translator v0.18.1 // indirect
        github.com/go-playground/validator/v10 v10.20.0 // indirect
        github.com/goccy/go-json v0.10.2 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/klauspost/cpuid/v2 v2.2.7 // indirect
        github.com/leodido/go-urn v1.4.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/pelletier/go-toml/v2 v2.2.2 // indirect
        github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
        github.com/ugorji/go/codec v1.2.12 // indirect
        golang.org/x/arch v0.8.0 // indirect
        golang.org/x/crypto v0.23.0 // indirect
        golang.org/x/net v0.25.0 // indirect
        golang.org/x/sys v0.20.0 // indirect
        golang.org/x/text v0.15.0 // indirect
        google.golang.org/protobuf v1.34.1 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
)

go.sum
#

  • golang-source-code/go.sum
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnSAmaUZd9HZYZSw9w/bFjp3FiNaJ5WmE=
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

Example HTML File
#

  • golang-source-code/views/index.html
<!DOCTYPE html>
<html>

<head>
        <title>jklug.work</title>
</head>

<body>
        <h1>Hi there from Go webserver<br></h1>
</body>

</html>



Verify Deployment
#

Verify the Container Deployment
#

Verify the deployed container:

# Verify the running container
docker ps

# Shell output:
CONTAINER ID   IMAGE                                                                                      COMMAND                  CREATED              STATUS              PORTS                                       NAMES
011daa23c2a8   gitlab-registry.jklug.work/go/go-webserver/main:6e58374aca498ee7376812d0b3b4f1fbd6f8078d   "./binary-example-fi…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   go-webserver

Verify container runs as non-root user:

# Access container terminal
docker exec -it 011daa23c2a8 sh

# List details of current user:
id

# Shell output:
uid=100(appuser) gid=65533(nogroup) groups=65533(nogroup)

List container image details:

# Verify the container images
docker images

# Shell output:
REPOSITORY                                                     TAG                                        IMAGE ID       CREATED              SIZE
gitlab-registry.jklug.work/go/go-webserver/main                6e58374aca498ee7376812d0b3b4f1fbd6f8078d   1a5353aa6ed1   About a minute ago   18.3MB

Verify the Go Webserver Application
#

# Curl the webserver
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html>

<head>
        <title>jklug.work</title>
</head>



Links #

jueklu/gitlab-ci-deploy-go-webserver

GitLab CI pipeline that builds, containerizes, and deploys a Go web application using the Gin framework.

Dockerfile
0
0
GitLab-CI-Go-Web-Application - This article is part of a series.
Part 1: This Article