Dalam membangun dan maintain aplikasi Go di production, CI/CD pipeline yang baik adalah essential. Berikut adalah best practices yang saya implementasikan di berbagai projects.
1. Basic Pipeline Structure
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
GO_VERSION: "1.21"
DOCKER_REGISTRY: "registry.gitlab.com"
before_script:
- echo "Starting pipeline for $CI_COMMIT_REF_NAME"
test:
stage: test
image: golang:${GO_VERSION}
script:
- go mod download
- go test -v -race -coverprofile=coverage.out ./...
- go tool cover -func=coverage.out
coverage: '/total:\s+\(statements\)\s+(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.out
lint:
stage: test
image: golangci/golangci-lint:latest
script:
- golangci-lint run -v
allow_failure: false
2. Multi-Stage Docker Build
Optimasi Docker image size sangat penting untuk deployment speed:
# Dockerfile
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
Result: Image size dari 800MB menjadi 15MB 🎉
3. Build & Push Docker Image
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
- develop
4. Deploy ke Staging/Production
deploy:staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
script:
- ssh -o StrictHostKeyChecking=no $STAGING_USER@$STAGING_HOST "
docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA &&
docker stop api-staging || true &&
docker rm api-staging || true &&
docker run -d
--name api-staging
--restart always
-p 8080:8080
-e DATABASE_URL=$STAGING_DB_URL
-e REDIS_URL=$STAGING_REDIS_URL
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
"
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy:production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
script:
- ssh -o StrictHostKeyChecking=no $PROD_USER@$PROD_HOST "
docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA &&
docker stop api-prod || true &&
docker rm api-prod || true &&
docker run -d
--name api-prod
--restart always
-p 8080:8080
-e DATABASE_URL=$PROD_DB_URL
-e REDIS_URL=$PROD_REDIS_URL
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
"
environment:
name: production
url: https://api.example.com
when: manual
only:
- main
5. Caching untuk Speed Up
test:
stage: test
image: golang:1.21
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .go/pkg/mod/
before_script:
- export GOPATH=$CI_PROJECT_DIR/.go
script:
- go mod download
- go test ./...
Result: Build time dari 5 menit menjadi 1.5 menit ⚡
6. Security Scanning
security:trivy:
stage: test
image: aquasec/trivy:latest
script:
- trivy image --exit-code 0 --severity LOW,MEDIUM $CI_REGISTRY_IMAGE:latest
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:latest
allow_failure: false
security:gosec:
stage: test
image: securego/gosec:latest
script:
- gosec ./...
allow_failure: true
7. Database Migrations
migrate:
stage: deploy
image: migrate/migrate
script:
- migrate -path ./migrations -database "$DATABASE_URL" up
only:
- main
when: manual
8. Rollback Strategy
rollback:production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client git
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
script:
- PREVIOUS_SHA=$(git rev-parse HEAD~1)
- ssh -o StrictHostKeyChecking=no $PROD_USER@$PROD_HOST "
docker pull $CI_REGISTRY_IMAGE:$PREVIOUS_SHA &&
docker stop api-prod &&
docker rm api-prod &&
docker run -d
--name api-prod
--restart always
-p 8080:8080
$CI_REGISTRY_IMAGE:$PREVIOUS_SHA
"
environment:
name: production
action: rollback
when: manual
only:
- main
Best Practices Summary
- Always run tests before deployment
- Use multi-stage Docker builds untuk optimize image size
- Cache dependencies untuk faster builds
- Implement security scanning di pipeline
- Manual approval untuk production deploys
- Always have rollback strategy
- Use environment variables untuk secrets
- Monitor pipeline metrics dan optimize bottlenecks
Real Impact
Di project Foxlogger, dengan implementasi CI/CD ini:
- ✅ Deployment time: 20 menit → 3 menit
- ✅ Zero-downtime deployments
- ✅ Automatic rollback on failure
- ✅ 100% test coverage enforcement
- ✅ Security vulnerabilities caught before production
Kesimpulan
CI/CD yang baik bukan hanya tentang automation, tapi juga tentang safety, speed, dan confidence dalam shipping code to production.
Invest time di setup awal, dan Anda akan save hours setiap minggu! 🚀
Questions? Feel free to reach out!