Go Development Environment with gopls & Modules

Introduction

Go’s language server (gopls) provides IDE integration, refactoring, and debugging capabilities. This section covers configuring Go DevContainers with gopls, Go modules for dependency management, and cross-platform builds.

Sections

1. Go Installation & Version Management

Install Go via devcontainer.json features with explicit version pinning (e.g., 1.21.0). Define GOPATH and GOROOT environment variables to ensure container-local Go is preferred.

Use multi-stage Dockerfiles to compile Go binaries efficiently. Install development headers and tools in build stages only.

2. Go Modules & Dependencies

Enable Go modules via GO111MODULE=on environment variable. Define go.mod and go.sum files in source control for deterministic dependency resolution.

Use exact version constraints in go.mod (e.g., require github.com/pkg/errors v0.9.1). Commit go.sum to Git to ensure supply chain reproducibility.

3. Language Server (gopls) Integration

Install gopls compatible with your Go version. Configure VS Code go.useLanguageServer: true and go.linterFlags to enable linting.

Use go.lintOnSave to enforce linting on file changes. Configure goimports to auto-add/remove missing imports.

4. Cross-Compilation & Multi-Platform Builds

Use GOOS and GOARCH environment variables to cross-compile for different platforms. Define build scripts that target common architectures (linux/amd64, linux/arm64, darwin/amd64).

Test cross-compiled binaries to ensure compatibility. Use file command to verify binary architecture.

Code Blocks

devcontainer.json with Go and gopls

{
  "image": "mcr.microsoft.com/devcontainers/go:1.21",
  "mounts": [
    "source=go-build-cache,target=/root/.cache/go-build,type=volume",
    "source=go-mod-cache,target=/root/go/pkg/mod,type=volume"
  ],
  "containerEnv": {
    "GO111MODULE": "on",
    "GOPROXY": "https://proxy.golang.org"
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "golang.Go@0.39.0"
      ],
      "settings": {
        "go.useLanguageServer": true,
        "go.lintOnSave": "package",
        "[go]": {
          "editor.formatOnSave": true,
          "editor.defaultFormatter": "golang.Go"
        }
      }
    }
  },
  "postCreateCommand": "go mod download"
}

Dockerfile with Go multi-stage build

FROM golang:1.21-alpine AS builder
RUN apk add --no-cache git make

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app ./cmd/main.go

FROM alpine:latest
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]

Build script for cross-compilation

#!/usr/bin/env bash
set -euo pipefail

# Cross-compile for multiple platforms
targets=("linux/amd64" "linux/arm64" "darwin/amd64" "windows/amd64")

for target in "${targets[@]}"; do
  os="${target%/*}"
  arch="${target#*/}"
  
  echo "Building for ${os}/${arch}..."
  
  GOOS="${os}" GOARCH="${arch}" CGO_ENABLED=0 go build \
    -o "dist/app-${os}-${arch}" \
    ./cmd/main.go
done

echo "✓ Cross-compilation complete"

Common Pitfalls

  • Go proxy misconfiguration: Missing or incorrect GOPROXY setting causes module downloads to fail. Use GOPROXY=https://proxy.golang.org.
  • gopls version mismatch: gopls incompatible with Go version causes IDE failures. Test gopls in fresh containers.
  • Missing go.sum entries: Forgetting go mod tidy before committing causes CI failures on fresh clones. Always run go mod tidy before pushing.
  • CGO in containers: Enabling CGO (CGO_ENABLED=1) requires system libraries. Use CGO_ENABLED=0 for portable builds.

FAQ

How do I ensure Go module consistency across environments? Commit go.mod and go.sum to Git. Use identical Go versions in CI and DevContainers. Run go mod download in postCreateCommand to pre-download modules.

What’s the best way to structure multi-service Go projects? Use Go workspaces or monorepo patterns with separate go.mod files per service. Define service dependencies explicitly. Use build scripts to handle multi-service builds.