VERSICH

Building Go App with GitLab Runner on Azure | Part 2

building go app with gitlab runner on azure | part 2

Welcome back! In Part 1, you set up an Azure VM, configured networking, installed Docker and Go, created a simple web server, registered a GitLab Runner, and built a basic CI pipeline. In Part 2, we'll take it further:

  • Enhance the Go app with Docker containerization

  • Extend the GitLab pipeline with testing, Docker image builds, and deployment

  • Automatically deploy the containerized app to the Azure VM

  • Add health checks and monitoring

Let's bring your pipeline to production-ready status!

1. Enhancing the Go Web Server

First, upgrade your Go app from a basic server to a multi-endpoint API with JSON responses and health checks.

Update ~/go-azure-app/main.go:

package main 
 
import ( 
"encoding/json" 
"fmt" 
"log" 
"net/http" 
"time" 
) 
 
type HealthResponse struct { 
Status    string    `json:"status"` 
Timestamp time.Time `json:"timestamp"` 
Version   string    `json:"version"` 
} 
 
func healthHandler(w http.ResponseWriter, r *http.Request) { 
response := HealthResponse{ 
Status:    "healthy", 
Timestamp: time.Now(), 
Version:   "1.0.0", 
} 
w.Header().Set("Content-Type", "application/json") 
json.NewEncoder(w).Encode(response) 
} 
 
func helloHandler(w http.ResponseWriter, r *http.Request) { 
fmt.Fprintln(w, "Hello from Go on Azure! 🚀") 
fmt.Fprintf(w, "\nPipeline build successful via GitLab Runner!") 
} 
 
func main() { 
http.HandleFunc("/health", healthHandler) 
http.HandleFunc("/", helloHandler) 
 
log.Println("🚀 Go server starting on :7000") 
log.Println("Endpoints: / (hello), /health (JSON)") 
 
if err := http.ListenAndServe(":7000", nil); err != nil { 
log.Fatal(err) 
} 
} 

Test locally on the VM:

cd ~/go-azure-app 
go mod tidy 
go run main.go

Visit http://<VM_IP>:7000 and http://<VM_IP>:7000/health to confirm that both endpoints work.

 

2. Creating a Dockerfile

Containerize your Go app to support consistent deployments.

Create ~/go-azure-app/Dockerfile:

# Build stage 
FROM golang:1.22-alpine AS builder 
WORKDIR /app 
COPY go.mod go.sum ./ 
RUN go mod download 
COPY . . 
RUN CGO_ENABLED=0 GOOS=linux go build -o main . 
 
# Runtime stage 
FROM alpine:latest 
RUN apk --no-cache add ca-certificates 
WORKDIR /root/ 
COPY --from=builder /app/main . 
USER 1000 
EXPOSE 7000 
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 
  CMD wget --no-verbose --tries=1 --spider http://localhost:7000/health || exit 1 
CMD ["./main"] 

 

3. Extending the GitLab CI Pipeline

Update .gitlab-ci.yml to build Docker images, run integration tests, and deploy:

stages: 
  - test 
  - build 
  - deploy 
 
variables: 
  GO_VERSION: "1.22" 
  APP_NAME: "go-azure-app" 
  DOCKER_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" 
  DOCKER_LATEST: "$CI_REGISTRY_IMAGE:latest" 
 
# Unit tests 
unit-test: 
  stage: test 
  image: golang:${GO_VERSION}-alpine 
  tags: 
    - go 
    - azure 
  script: 
    - go mod tidy 
    - go test ./... -v 
  cache: 
    paths: 
      - /go/pkg/mod 
 
# Integration tests (spin up container and test endpoints) 
integration-test: 
  stage: test 
  image: docker:25-git 
  services: 
    - docker:25-dind 
  tags: 
    - go 
    - azure 
  variables: 
    DOCKER_HOST: tcp://docker:2376 
    DOCKER_TLS_CERTDIR: "" 
    DOCKER_DRIVER: overlay2 
  script: 
    - docker build -t ${APP_NAME}:test . 
    - docker run -d -p 7000:7000 --name test-app ${APP_NAME}:test 
    - sleep 5 
    - docker exec test-app wget --spider -q http://localhost:7000/health || exit 1 
    - docker stop test-app && docker rm test-app 
 
# Build and push Docker image 
docker-build: 
  stage: build 
  image: docker:25-git 
  services: 
    - docker:25-dind 
  tags: 
    - go 
    - azure 
  variables: 
    DOCKER_HOST: tcp://docker:2376 
    DOCKER_TLS_CERTDIR: "" 
  script: 
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 
    - docker build -t $DOCKER_IMAGE . 
    - docker push $DOCKER_IMAGE 
    - docker tag $DOCKER_IMAGE $DOCKER_LATEST 
    - docker push $DOCKER_LATEST 
  only: 
    - main 
 
# Deploy to Azure VM 
deploy-azure-vm: 
  stage: deploy 
  image: alpine:latest 
  tags: 
    - go 
    - azure 
  before_script: 
    - apk add --no-cache openssh-client rsync 
    - eval $(ssh-agent -s) 
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - 
    - mkdir -p ~/.ssh 
    - chmod 700 ~/.ssh 
  script: 
    - ssh -o StrictHostKeyChecking=no azureuser@$VM_IP "
        docker pull $DOCKER_LATEST && 
        docker stop $APP_NAME || true && 
        docker rm $APP_NAME || true && 
        docker run -d  
          --name $APP_NAME  
          -p 7000:7000  
          --restart unless-stopped  
          $DOCKER_LATEST" 
  only: 
    - main 
  environment: 
    name: production 
    url: http://$VM_IP:7000

Key additions:

  • Integration tests verify that the Dockerized app works

  • Docker build/push to GitLab Container Registry

  • SSH deployment to your Azure VM

 

4. Setting Up Deployment Variables

Configure secure deployment in GitLab:

  1. Go to Settings → CI/CD → Variables in your GitLab project

  1. Add these protected variables:

  • SSH_PRIVATE_KEY: Your Azure VM private key (full PEM content)

  • VM_IP: Your Azure VM public IP address

 

5. Committing and Running the Pipeline

Push the updates:

cd ~/go-azure-app 
git add main.go Dockerfile .gitlab-ci.yml 
git commit -m "Add Docker support, integration tests, and VM deployment" 
git push origin main 

Watch the pipeline run in GitLab:

  1. unit-test: Go unit tests pass

  1. integration-test: Docker container starts and the health check passes

  1. docker-build: Image builds and pushes to the registry

  1. deploy-azure-vm: App deploys to the Azure VM

 

6. Verifying Production Deployment

After a successful deployment:

  1. Visit http://<VM_IP>:7000 to see the updated hello message

  1. Visit http://<VM_IP>:7000/health to receive the JSON health response

  1. Check container status: run docker ps on the VM

  1. Test resilience: run docker restart go-azure-app, then recheck the endpoints

Production Monitoring Commands (run on the VM):

# Check logs 
docker logs -f go-azure-app 
 
# Health status 
curl http://localhost:7000/health 
 
# Restart policy test 
docker restart go-azure-app

7. Pipeline Status and Environments

GitLab now tracks your production environment:

What You've Built in Part 2

  • Enhanced Go API with a JSON health endpoint
  • Multi-stage Docker build (builder + runtime)
  • Comprehensive GitLab pipeline: tests → build → deploy
  • Zero-downtime deployment to the Azure VM
  • Production monitoring with health checks and restarts
  • GitLab Container Registry integration

Your setup is now configured to:

  • Test every code change

  • Build optimized Docker images

  • Deploy to production on the main branch

  • Recover from VM restarts and failures

 

Tags: