# Dockerfile# Build stageFROMgolang:1.21-alpineASbuilder# Install build dependenciesRUNapkadd--no-cachegitca-certificatestzdata
# Set working directoryWORKDIR/app# Copy go mod filesCOPYgo.modgo.sum./
# Download dependenciesRUNgomoddownload
# Copy source codeCOPY..
# Build the applicationRUNCGO_ENABLED=0GOOS=linuxgobuild-a-installsuffixcgo-omain./cmd/server
# Final stageFROMalpine:latest# Install ca-certificates for HTTPSRUNapk--no-cacheaddca-certificatestzdata
# Create non-root userRUNaddgroup-g1001-Sappgroup&&\adduser-u1001-Sappuser-Gappgroup
# Set working directoryWORKDIR/app# Copy the binary from builder stageCOPY--from=builder/app/main.
# Change ownershipRUNchown-Rappuser:appgroup/app
# Switch to non-root userUSERappuser# Expose portEXPOSE8080# Health checkHEALTHCHECK--interval=30s--timeout=3s--start-period=5s--retries=3\CMDwget--no-verbose--tries=1--spiderhttp://localhost:8080/health||exit1# Run the applicationCMD["./main"]
# Dockerfile.secure# Build stageFROMgolang:1.21-alpineASbuilder# Install security toolsRUNapkadd--no-cache\git\ca-certificates\tzdata\gosu\dumb-init
# Create build userRUNaddgroup-g1001-Sbuildgroup&&\adduser-u1001-Sbuilduser-Gbuildgroup
# Set working directoryWORKDIR/app# Copy go mod filesCOPYgo.modgo.sum./
# Download dependenciesRUNgomoddownload
# Copy source codeCOPY..
# Build with security flagsRUNCGO_ENABLED=0GOOS=linuxgobuild\-a-installsuffixcgo\-ldflags='-w -s -extldflags "-static"'\-buildmode=pie\-omain./cmd/server
# Security scanRUNapkadd--no-cachesecurity-scan-tool
RUNsecurity-scan-tool/app/main
# Final stageFROMalpine:latest# Install security packagesRUNapkadd--no-cache\ca-certificates\tzdata\gosu\dumb-init\&&rm-rf/var/cache/apk/*
# Create application userRUNaddgroup-g1001-Sappgroup&&\adduser-u1001-Sappuser-Gappgroup
# Set working directoryWORKDIR/app# Copy the binaryCOPY--from=builder/app/main.
# Change ownershipRUNchown-Rappuser:appgroup/app
# Create necessary directoriesRUNmkdir-p/app/logs/app/data&&\chown-Rappuser:appgroup/app/logs/app/data
# Switch to non-root userUSERappuser# Expose portEXPOSE8080# Health checkHEALTHCHECK--interval=30s--timeout=3s--start-period=5s--retries=3\CMDwget--no-verbose--tries=1--spiderhttp://localhost:8080/health||exit1# Use dumb-init for proper signal handlingENTRYPOINT["dumb-init","--"]# Run the applicationCMD["./main"]
// internal/health/container_health.gopackagehealthimport("context""fmt""net/http""os""runtime""time")// ContainerHealth represents container health statustypeContainerHealthstruct{Statusstring`json:"status"`Timestamptime.Time`json:"timestamp"`Versionstring`json:"version"`Uptimetime.Duration`json:"uptime"`MemoryMemoryStats`json:"memory"`Goroutinesint`json:"goroutines"`Environmentmap[string]string`json:"environment"`}// MemoryStats represents memory statisticstypeMemoryStatsstruct{Allocuint64`json:"alloc"`TotalAllocuint64`json:"total_alloc"`Sysuint64`json:"sys"`NumGCuint32`json:"num_gc"`}// HealthChecker represents a health checkertypeHealthCheckerstruct{startTimetime.Timeversionstring}// NewHealthChecker creates a new health checkerfuncNewHealthChecker(versionstring)*HealthChecker{return&HealthChecker{startTime:time.Now(),version:version,}}// CheckHealth checks the health of the containerfunc(hc*HealthChecker)CheckHealth()ContainerHealth{varmruntime.MemStatsruntime.ReadMemStats(&m)returnContainerHealth{Status:"healthy",Timestamp:time.Now(),Version:hc.version,Uptime:time.Since(hc.startTime),Memory:MemoryStats{Alloc:m.Alloc,TotalAlloc:m.TotalAlloc,Sys:m.Sys,NumGC:m.NumGC,},Goroutines:runtime.NumGoroutine(),Environment:map[string]string{"GOOS":runtime.GOOS,"GOARCH":runtime.GOARCH,"GOMAXPROCS":fmt.Sprintf("%d",runtime.GOMAXPROCS(0)),},}}// HealthHandler handles health check requestsfunc(hc*HealthChecker)HealthHandler(whttp.ResponseWriter,r*http.Request){health:=hc.CheckHealth()w.Header().Set("Content-Type","application/json")w.WriteHeader(http.StatusOK)json.NewEncoder(w).Encode(health)}// ReadinessHandler handles readiness check requestsfunc(hc*HealthChecker)ReadinessHandler(whttp.ResponseWriter,r*http.Request){// Check if the application is ready to serve trafficifhc.isReady(){w.WriteHeader(http.StatusOK)w.Write([]byte("ready"))}else{w.WriteHeader(http.StatusServiceUnavailable)w.Write([]byte("not ready"))}}// LivenessHandler handles liveness check requestsfunc(hc*HealthChecker)LivenessHandler(whttp.ResponseWriter,r*http.Request){// Check if the application is aliveifhc.isAlive(){w.WriteHeader(http.StatusOK)w.Write([]byte("alive"))}else{w.WriteHeader(http.StatusInternalServerError)w.Write([]byte("not alive"))}}// isReady checks if the application is readyfunc(hc*HealthChecker)isReady()bool{// Implement readiness checksreturntrue}// isAlive checks if the application is alivefunc(hc*HealthChecker)isAlive()bool{// Implement liveness checksreturntrue}
# docker-compose.dev.ymlversion:'3.8'services:app:build:context:.dockerfile:Dockerfile.devimage:myapp:devcontainer_name:myapp-devrestart:unless-stopped# Development settingsenvironment:-GIN_MODE=debug-LOG_LEVEL=debug-TZ=UTC# Volumes for developmentvolumes:-.:/app-/app/vendor-app-logs:/app/logs# Portsports:-"8080:8080"-"2345:2345"# Delve debugger# Networknetworks:-dev-network# Health checkhealthcheck:test:["CMD","wget","--no-verbose","--tries=1","--spider","http://localhost:8080/health"]interval:30stimeout:10sretries:3start_period:40s# Database for developmentpostgres:image:postgres:15-alpinecontainer_name:postgres-devrestart:unless-stoppedenvironment:-POSTGRES_DB=myapp-POSTGRES_USER=myapp-POSTGRES_PASSWORD=myappvolumes:-postgres-data:/var/lib/postgresql/dataports:-"5432:5432"networks:-dev-network# Redis for developmentredis:image:redis:7-alpinecontainer_name:redis-devrestart:unless-stoppedvolumes:-redis-data:/dataports:-"6379:6379"networks:-dev-networknetworks:dev-network:driver:bridgevolumes:app-logs:driver:localpostgres-data:driver:localredis-data:driver:local
// internal/testing/container_test.gopackagetestingimport("context""testing""time""github.com/docker/docker/api/types""github.com/docker/docker/client""github.com/stretchr/testify/assert""github.com/stretchr/testify/require")funcTestContainerBuild(t*testing.T){ctx:=context.Background()// Create Docker clientcli,err:=client.NewClientWithOpts(client.FromEnv,client.WithAPIVersionNegotiation())require.NoError(t,err)defercli.Close()// Build imagebuildCtx:=createBuildContext()resp,err:=cli.ImageBuild(ctx,buildCtx,types.ImageBuildOptions{Tags:[]string{"myapp:test"},})require.NoError(t,err)deferresp.Body.Close()// Verify image existsimages,err:=cli.ImageList(ctx,types.ImageListOptions{})require.NoError(t,err)varfoundboolfor_,image:=rangeimages{for_,tag:=rangeimage.RepoTags{iftag=="myapp:test"{found=truebreak}}}assert.True(t,found,"Image should exist")}funcTestContainerRun(t*testing.T){ctx:=context.Background()// Create Docker clientcli,err:=client.NewClientWithOpts(client.FromEnv,client.WithAPIVersionNegotiation())require.NoError(t,err)defercli.Close()// Run containerresp,err:=cli.ContainerCreate(ctx,&types.ContainerConfig{Image:"myapp:test",ExposedPorts:map[string]struct{}{"8080/tcp":{},},},&types.HostConfig{PortBindings:map[string][]types.PortBinding{"8080/tcp":{{HostPort:"8080"}},},},nil,nil,"myapp-test")require.NoError(t,err)// Start containererr=cli.ContainerStart(ctx,resp.ID,types.ContainerStartOptions{})require.NoError(t,err)defercli.ContainerStop(ctx,resp.ID,nil)// Wait for container to be readytime.Sleep(5*time.Second)// Test health checkhealth,err:=cli.ContainerInspect(ctx,resp.ID)require.NoError(t,err)assert.Equal(t,"running",health.State.Status)}funccreateBuildContext()io.Reader{// Create build contextreturnnil}