Skip to content

Go Development Environment Best Practices

Objective: Master senior-level Go development environment setup and operation across macOS, Linux, and Windows (WSL and native). Copy-paste runnable, auditable, and production-ready.

Core Principles

  • Reproducible Builds: Lock Go versions, dependencies, and toolchains
  • Fast Feedback Loops: Hot reloading, instant testing, and rapid iteration
  • Cross-Platform Consistency: Works identically on macOS, Linux, and Windows
  • Security First: Secure dependency management and supply chain integrity
  • Performance Optimized: Fast compilation, efficient tooling, and minimal overhead

Environment Setup

Go Version Management

# Install g (Go version manager)
curl -sSL https://git.io/g-install | sh -s

# Install latest Go
g install latest

# Use specific version for project
g use 1.21.5

# Verify installation
go version

Project Structure

my-go-project/
├── cmd/                    # Application entry points
│   ├── server/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/              # Private application code
│   ├── api/
│   ├── config/
│   └── service/
├── pkg/                   # Public library code
│   ├── client/
│   └── utils/
├── api/                    # API definitions
│   └── openapi.yaml
├── scripts/               # Build and deployment scripts
├── testdata/              # Test fixtures
├── go.mod
├── go.sum
├── Makefile
├── .golangci.yml
├── .gitignore
└── README.md

Essential Tools

# Install essential Go tools
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/air-verse/air@latest
go install github.com/golang/mock/mockgen@latest
go install github.com/swaggo/swag/cmd/swag@latest
go install github.com/cosmtrek/air@latest
go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest

Development Workflow

Hot Reloading with Air

# .air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ./cmd/server"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_root = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true

Linting Configuration

# .golangci.yml
run:
  timeout: 5m
  issues-exit-code: 1
  tests: true
  skip-dirs:
    - vendor
    - testdata
  skip-files:
    - ".*\\.pb\\.go$"

linters-settings:
  gocyclo:
    min-complexity: 15
  goconst:
    min-len: 3
    min-occurrences: 3
  gocritic:
    enabled-tags:
      - diagnostic
      - experimental
      - opinionated
      - performance
      - style
  govet:
    check-shadowing: true
  gosec:
    severity: medium
    confidence: medium
  misspell:
    locale: US
  lll:
    line-length: 120
  funlen:
    lines: 100
    statements: 50
  gocognit:
    min-complexity: 20

linters:
  enable:
    - bodyclose
    - deadcode
    - depguard
    - dogsled
    - dupl
    - errcheck
    - exportloopref
    - exhaustive
    - funlen
    - gochecknoinits
    - goconst
    - gocritic
    - gocyclo
    - gofmt
    - goimports
    - gomnd
    - goprintffuncname
    - gosec
    - gosimple
    - govet
    - ineffassign
    - lll
    - misspell
    - nakedret
    - noctx
    - nolintlint
    - rowserrcheck
    - staticcheck
    - structcheck
    - stylecheck
    - typecheck
    - unconvert
    - unparam
    - unused
    - varcheck
    - whitespace

issues:
  exclude-rules:
    - path: _test\.go
      linters:
        - gomnd
        - funlen
        - goconst

Testing Configuration

// internal/testing/testutil.go
package testing

import (
    "context"
    "database/sql"
    "testing"
    "time"

    "github.com/DATA-DOG/go-sqlmock"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

// TestDB creates a mock database for testing
func TestDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock) {
    db, mock, err := sqlmock.New()
    require.NoError(t, err)

    t.Cleanup(func() {
        db.Close()
    })

    return db, mock
}

// TestContext creates a test context with timeout
func TestContext(t *testing.T) context.Context {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    t.Cleanup(cancel)
    return ctx
}

// AssertNoError is a helper for common error assertions
func AssertNoError(t *testing.T, err error) {
    t.Helper()
    assert.NoError(t, err)
}

// AssertError is a helper for error assertions
func AssertError(t *testing.T, err error) {
    t.Helper()
    assert.Error(t, err)
}

Build and Deployment

Makefile

# Makefile
.PHONY: help build test lint clean docker

# Default target
help: ## Show this help message
    @echo "Available targets:"
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

# Build targets
build: ## Build the application
    @echo "Building application..."
    go build -o bin/server ./cmd/server
    go build -o bin/worker ./cmd/worker

build-linux: ## Build for Linux
    @echo "Building for Linux..."
    GOOS=linux GOARCH=amd64 go build -o bin/server-linux ./cmd/server
    GOOS=linux GOARCH=amd64 go build -o bin/worker-linux ./cmd/worker

# Testing
test: ## Run tests
    @echo "Running tests..."
    go test -v -race -coverprofile=coverage.out ./...

test-coverage: ## Run tests with coverage
    @echo "Running tests with coverage..."
    go test -v -race -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out -o coverage.html

benchmark: ## Run benchmarks
    @echo "Running benchmarks..."
    go test -bench=. -benchmem ./...

# Linting
lint: ## Run linters
    @echo "Running linters..."
    golangci-lint run

lint-fix: ## Run linters with auto-fix
    @echo "Running linters with auto-fix..."
    golangci-lint run --fix

# Security
security: ## Run security checks
    @echo "Running security checks..."
    gosec ./...

# Development
dev: ## Start development server with hot reload
    @echo "Starting development server..."
    air

# Docker
docker: ## Build Docker image
    @echo "Building Docker image..."
    docker build -t my-go-app:latest .

docker-run: ## Run Docker container
    @echo "Running Docker container..."
    docker run -p 8080:8080 my-go-app:latest

# Cleanup
clean: ## Clean build artifacts
    @echo "Cleaning build artifacts..."
    rm -rf bin/
    rm -f coverage.out coverage.html
    go clean

# Dependencies
deps: ## Download dependencies
    @echo "Downloading dependencies..."
    go mod download
    go mod tidy

deps-update: ## Update dependencies
    @echo "Updating dependencies..."
    go get -u ./...
    go mod tidy

# Code generation
generate: ## Generate code
    @echo "Generating code..."
    go generate ./...

# Documentation
docs: ## Generate documentation
    @echo "Generating documentation..."
    swag init -g cmd/server/main.go -o api/docs

Docker Configuration

# Dockerfile
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata

# Copy go mod files
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server

# Final stage
FROM alpine:latest

# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates tzdata

WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Expose port
EXPOSE 8080

# Run the application
CMD ["./main"]

Multi-stage Docker Build

# Dockerfile.multi
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata

# Copy go mod files
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server

# Test stage
FROM builder AS tester

# Run tests
RUN go test -v -race -coverprofile=coverage.out ./...

# Final stage
FROM alpine:latest

# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates tzdata

WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Expose port
EXPOSE 8080

# Run the application
CMD ["./main"]

CI/CD Pipeline

GitHub Actions

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'

    - name: Cache Go modules
      uses: actions/cache@v3
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-

    - name: Download dependencies
      run: go mod download

    - name: Verify dependencies
      run: go mod verify

    - name: Run tests
      run: go test -v -race -coverprofile=coverage.out ./...

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.out

    - name: Run linters
      uses: golangci/golangci-lint-action@v3
      with:
        version: latest

    - name: Run security checks
      uses: securecodewarrior/github-action-gosec@master
      with:
        args: './...'

    - name: Build
      run: go build -o bin/server ./cmd/server

    - name: Build Docker image
      run: docker build -t my-go-app:${{ github.sha }} .

Performance Optimization

Profiling Configuration

// cmd/server/main.go
package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // Enable pprof endpoints
    go func() {
        log.Println("Starting pprof server on :6060")
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // Graceful shutdown
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)

    // Start your application
    // ...

    <-c
    log.Println("Shutting down gracefully...")
}

Benchmarking

// internal/service/benchmark_test.go
package service

import (
    "testing"
)

func BenchmarkProcessData(b *testing.B) {
    service := NewService()
    data := generateTestData(1000)

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        service.ProcessData(data)
    }
}

func BenchmarkConcurrentProcess(b *testing.B) {
    service := NewService()
    data := generateTestData(1000)

    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            service.ProcessData(data)
        }
    })
}

Security Best Practices

Dependency Security

# Check for vulnerabilities
go list -json -deps ./... | nancy sleuth

# Update dependencies
go get -u ./...
go mod tidy

# Audit dependencies
gosec ./...

Secure Configuration

// internal/config/config.go
package config

import (
    "os"
    "strconv"
    "time"
)

type Config struct {
    Server   ServerConfig
    Database DatabaseConfig
    Security SecurityConfig
}

type ServerConfig struct {
    Port         string
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
}

type DatabaseConfig struct {
    Host     string
    Port     int
    Username string
    Password string
    SSLMode  string
}

type SecurityConfig struct {
    JWTSecret     string
    BcryptCost    int
    RateLimitRPS  int
}

func Load() (*Config, error) {
    return &Config{
        Server: ServerConfig{
            Port:         getEnv("SERVER_PORT", "8080"),
            ReadTimeout:  getDuration("SERVER_READ_TIMEOUT", 30*time.Second),
            WriteTimeout: getDuration("SERVER_WRITE_TIMEOUT", 30*time.Second),
        },
        Database: DatabaseConfig{
            Host:     getEnv("DB_HOST", "localhost"),
            Port:     getInt("DB_PORT", 5432),
            Username: getEnv("DB_USERNAME", "postgres"),
            Password: getEnv("DB_PASSWORD", ""),
            SSLMode:  getEnv("DB_SSL_MODE", "require"),
        },
        Security: SecurityConfig{
            JWTSecret:    getEnv("JWT_SECRET", ""),
            BcryptCost:   getInt("BCRYPT_COST", 12),
            RateLimitRPS: getInt("RATE_LIMIT_RPS", 100),
        },
    }, nil
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

func getInt(key string, defaultValue int) int {
    if value := os.Getenv(key); value != "" {
        if intValue, err := strconv.Atoi(value); err == nil {
            return intValue
        }
    }
    return defaultValue
}

func getDuration(key string, defaultValue time.Duration) time.Duration {
    if value := os.Getenv(key); value != "" {
        if duration, err := time.ParseDuration(value); err == nil {
            return duration
        }
    }
    return defaultValue
}

TL;DR Runbook

Quick Start

# 1. Install Go version manager
curl -sSL https://git.io/g-install | sh -s

# 2. Install latest Go
g install latest

# 3. Install essential tools
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/air-verse/air@latest

# 4. Create new project
mkdir my-go-project && cd my-go-project
go mod init github.com/username/my-go-project

# 5. Start development
air

Essential Commands

# Development
make dev          # Start with hot reload
make test         # Run tests
make lint         # Run linters
make build        # Build application

# Production
make docker       # Build Docker image
make security     # Run security checks
make benchmark    # Run benchmarks

This guide provides the complete machinery for setting up a production-ready Go development environment. Each pattern includes configuration examples, tooling setup, and real-world implementation strategies for enterprise deployment.