Caching Go module downloads with a named volume

By default the Go module cache ($GOPATH/pkg/mod) and build cache live inside the container’s writable layer, so every DevContainer rebuild discards them and the next go build re-downloads and recompiles the entire dependency graph. This page moves both caches into named volumes so rebuilds are near-instant and offline-capable. It extends the Go Development Environment with gopls & Modules cluster.

Prerequisites

  • Go 1.22+ in the container
  • A non-root remoteUser (e.g. vscode) that owns the cache paths
  • Docker named-volume support

How-To Steps

  1. Mount the module and build caches as named volumes and point Go at them:

    {
      "image": "mcr.microsoft.com/devcontainers/go:1.22@sha256:b91a...",
      "containerEnv": {
        "GOPATH": "/home/vscode/go",
        "GOCACHE": "/home/vscode/.cache/go-build"
      },
      "mounts": [
        "source=go-mod-cache,target=/home/vscode/go/pkg/mod,type=volume",
        "source=go-build-cache,target=/home/vscode/.cache/go-build,type=volume"
      ],
      "remoteUser": "vscode"
    }
    
  2. Fix ownership of the mounted volumes on first create, since fresh volumes are root-owned:

    { "postCreateCommand": "sudo chown -R vscode:vscode /home/vscode/go /home/vscode/.cache/go-build" }
    
  3. Warm the cache once so subsequent rebuilds reuse it:

    go mod download
    go build ./...
    
  4. Confirm a rebuild reuses the cache — recreate the container and time a build:

    devcontainer up --workspace-folder . --remove-existing-container
    devcontainer exec --workspace-folder . bash -c "time go build ./..."   # second build should be seconds, not minutes
    

Common Pitfalls

SymptomRoot CauseRemediation
Cache empty after every rebuildCache in the container layer, not a volumeMount pkg/mod and go-build as named volumes
permission denied writing cacheFresh volume owned by rootchown the paths in postCreateCommand
Build still slow on second runGOCACHE not redirected to the volumeSet GOCACHE in containerEnv
Stale module versions servedCache holds a yanked versiongo clean -modcache once, then re-download

Conclusion

The invariant: both GOPATH/pkg/mod and GOCACHE must live on persistent named volumes the remoteUser owns. Redirect them with containerEnv, fix ownership once, and rebuilds reuse every download and compiled object instead of starting cold.

FAQ

Why two volumes instead of one? The module cache (pkg/mod) holds downloaded source; the build cache (GOCACHE) holds compiled objects. They grow and invalidate independently, so separate volumes keep eviction clean.

Can I share these volumes between projects? Yes — the module cache is content-addressed and safe to share. The build cache is also keyed by content, so cross-project sharing is correct and saves recompilation.