Kat Morgan
May 12, 2025

๐Ÿ”ง Golang installation and management in 2025 ๐Ÿš€

Posted on May 12, 2025  •  15 minutes  • 2998 words
Table of contents

Starting new projects can be overwhelming, especially when switching to long-forgotten or even new emerging skillsets, languages, and tools.

In recent weeks I’ve picked up new greenfield Go projects, and I realized it has been almost 4 years since I last started a new project in this language. Given the velocity of the developer tooling ecosystem, it felt prudent to research the latest trends and best practices to re-skill with the advantage of the latest tools and techniques.

After a dive into the interwebs and coming back with a number of pleasant surprises, I’ve settled on an approach leaning heavily on OS package manager for base Go installation, mise for version management, and containers for development and production encapsulation. ๐ŸŽ‰

I’ve included a healthy dose of basics to help new audiences get up and running, but as broad as the reach of this guide has become, I hope that even seasoned developers will walk away with something new to get excited about too.

Golang Installation and Management in 2025Multi-Module WorkspacesVS Code ConfigurationVim/Neovim SetupUpdating System GoUpdating Mise GoEnvironment VariablesDevContainer SetupUpdating ToolsMulti-Stage BuildsUpdating DependenciesTool DependenciesHot-ReloadingSystem Package ManagersAutomatic Version SwitchingShared Code ApproachManual Version SwitchingMulti-Module MonorepoMulti-Environment Docker-ComposeVS Code IntegrationVulnerability ScanningVersion ManagersIDE and Editor SetupMaintenance and UpdatesContainer IntegrationGolangInstallationandManagementAdvanced Patterns forMicroservicesVersion SwitchingDependency ManagementWorkspace ConfigurationInstallation Options

๐Ÿ“‘ Table of Contents

๐Ÿ’ก Bottom Line

The most intuitive, reliable, and portable way to manage Go in 2025 is to install Go via your system’s package manager as a base version, then use mise for version management across projects. This setup offers excellent container integration, supports multiple Go versions, and works seamlessly in both local and containerized environments.

For teams working on microservices with both frontend and backend components, this approach provides the perfect balance of performance, flexibility, and tooling compatibility. ๐Ÿ’ช

๐Ÿ” Introduction: Why Go installation matters

The Go installation approach directly impacts how easily developers can switch between projects, maintain consistent environments across teams, and update toolchains. A properly configured system lets developers focus on writing code rather than fighting environment issues or dependency problems.

The decision is complicated by containerization, multiple Go versions, and the need to support both local and containerized development. A good setup must address all these concerns while remaining straightforward for new team members to adopt.

๐Ÿ› ๏ธ Installation options

๐Ÿ“ฆ System package managers

# Install Homebrew if not already installed
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install Go
brew install go

Ubuntu/Debian (APT)

sudo apt update && sudo apt install -y golang-go

This installs the distro’s default Go version (e.g., Ubuntu 24.04 LTS includes Go 1.22.2). For newer Go versions on Ubuntu, consider using a PPA or Snap (e.g., sudo snap install go --classic).

Fedora (DNF)

sudo dnf install -y golang

Fedora tends to update Go relatively quickly compared to Ubuntu.

Red Hat UBI 9 / RHEL 9 (YUM/DNF)

# Enable codeready-builder repo if not already, then:
sudo yum module install -y go-toolset

This provides the system with a default Go installation that’s easy to maintain and update.

๐Ÿ”„ Version managers

Several specialized tools exist for managing multiple Go installations:

  1. mise (formerly rtx): Modern Rust-based cross-language version manager
  2. asdf with golang plugin : Mature cross-language manager with rich plugin ecosystem
  3. g (formerly goup): Lightweight Go-specific version manager
  4. gvm : Full-featured Go-specific version manager with environment isolation
  5. goenv : Minimalist Go version manager based on rbenv/pyenv model

After exploration, I’ve settled on mise as the most intuitive solution.

๐Ÿน Golang & mise installation

For microservice development, particularly for microservices with both frontend and backend components, the most effective approach combines a system Go installation with mise for project-specific version management.

1๏ธโƒฃ Step 1: Install Go with the system package manager

See the Installation options compared section for platform-specific commands.

2๏ธโƒฃ Step 2: Install mise for version management

# Install mise
curl https://mise.run | sh

# Add to shell (for bash)
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
source ~/.bashrc

# For zsh (macOS default)
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

# Install specific Go versions
mise install [email protected]
mise use --global [email protected]

3๏ธโƒฃ Step 3: Configure project-specific versions

For each project, create a .mise.toml file in the project root:

# .mise.toml
[tools]
go = "1.24.4"

Or use the mise cli:

cd ~/projects/myproject
mise use [email protected]

This will create a .mise.toml file automatically.

๐Ÿ† Why this approach wins

  1. Simplicity: Easy to set up and understand
  2. Performance: Mise directly modifies PATH instead of using shims, providing better performance
  3. Multiple languages: Supports other languages the team might use (Node.js, Python, etc.)
  4. Container compatibility: Works well with Docker and devcontainers
  5. Active development: Regular updates and improvements
  6. Project isolation: Automatic version switching based on directory

๐Ÿ“Š Dependency management best practices

Go’s module system has matured significantly, with Go 1.24 introducing key improvements like the tool directive for managing developer tools.

๐Ÿ’Ž Core recommendations

  1. Enable module mode (default in Go 1.24+):

    go env -w GO111MODULE=on
    
  2. Use the Go module proxy for better security and reliability:

    go env -w GOPROXY=https://proxy.golang.org,direct
    
  3. Pin versions for critical dependencies in the go.mod file:

    require (
        github.com/example/critical v1.2.3 // Pinned version for stability
        github.com/example/flexible v1.2.0 // Minimum version, accepts compatible updates
    )
    
  4. Implement security scanning in the workflow:

    # Install govulncheck
    go install golang.org/x/vuln/cmd/govulncheck@latest
    
    # Scan project for vulnerabilities
    govulncheck ./...
    

๐Ÿงฐ Tool dependencies (Go 1.24+)

One of the most exciting additions to Go 1.24 is the tool directive in go.mod, which formalizes tool dependency management. This eliminates the old tools.go hack with blank imports that many projects used.

Here’s how it works:

// In go.mod
module example.com/myproject

go 1.24

// Tool dependencies
tool golang.org/x/tools/cmd/stringer v0.4.0
tool golang.org/x/vuln/cmd/govulncheck v1.1.0
tool honnef.co/go/tools/cmd/staticcheck v0.5.1

To add a tool dependency:

go get -tool github.com/golang/mock/[email protected]

To run a tool:

go tool stringer -type=MyEnum

This approach cleanly separates build dependencies from dev tools, ensuring everyone on the team has the exact same tool versions without manual installation steps. The tools are downloaded to your module cache and can be vendored if needed.

๐Ÿ”’ Vulnerability scanning with govulncheck

Security is an engineering priority, and Go provides built-in tools to help.

The govulncheck tool checks your project’s dependencies against a vulnerability database and can tell you not just which packages have vulnerabilities, but whether your code actually calls the vulnerable functions: ๐Ÿ›ก๏ธ

# Add govulncheck as a tool dependency
go get -tool golang.org/x/vuln/cmd/govulncheck
go mod tidy

# Run the vulnerability check
go tool govulncheck ./...

This is a huge improvement over generic security scanners and should be part of your CI pipeline as well as pre-commit checks.

๐Ÿ“‚ Go workspace configuration

Modern Go development uses modules and workspaces instead of the traditional GOPATH model.

๐ŸŒฑ Essential environment variables

Add the following to your shell profile (.zshrc for most macOS systems, .bashrc or .zshrc for Linux):

# Go environment variables
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

๐Ÿงฉ Multi-module workspaces with go.work

For projects with multiple related modules, use Go’s workspace mode:

# Create a workspace directory
mkdir myworkspace
cd myworkspace

# Initialize individual modules
mkdir module1 module2
cd module1
go mod init github.com/yourusername/module1
cd ../module2
go mod init github.com/yourusername/module2
cd ..

# Create a workspace file
go work init module1 module2

This creates a go.work file that allows working on multiple modules simultaneously. Changes in one module are immediately visible to others without publishing.

Best practice: Add go.work and go.work.sum to .gitignore as they’re generally for local development only.

๐Ÿ—๏ธ Real-world microservices structure

myproject/
โ”œโ”€โ”€ go.work                 # Workspace file (local development only)
โ”œโ”€โ”€ common/                 # Shared code
โ”‚   โ”œโ”€โ”€ go.mod
โ”‚   โ””โ”€โ”€ pkg/
โ”‚       โ”œโ”€โ”€ auth/
โ”‚       โ””โ”€โ”€ models/
โ”œโ”€โ”€ services/
โ”‚   โ”œโ”€โ”€ user-service/       # Backend microservice
โ”‚   โ”‚   โ”œโ”€โ”€ go.mod
โ”‚   โ”‚   โ”œโ”€โ”€ main.go
โ”‚   โ”‚   โ””โ”€โ”€ api/
โ”‚   โ””โ”€โ”€ payment-service/    # Another backend microservice
โ”‚       โ”œโ”€โ”€ go.mod
โ”‚       โ”œโ”€โ”€ main.go
โ”‚       โ””โ”€โ”€ api/
โ””โ”€โ”€ web/                    # Frontend components
    โ”œโ”€โ”€ go.mod
    โ”œโ”€โ”€ main.go
    โ””โ”€โ”€ templates/

๐Ÿ’ป IDE and editor setup

๐Ÿ”ท VS Code configuration

VS Code with the Go extension remains the most popular editor for Go development on both macOS and Linux.

  1. Install the VSCode Go extension (by the Go Team at Google)

    • GUI: Open command palette (Cmd+Shift+P) and type “Install Extension”. Search for “Go” and install.
    • CLI: code --install-extension golang.go
  2. Add these settings to settings.json:

{
    "go.useLanguageServer": true,
    "go.toolsManagement.autoUpdate": true,
    "go.formatTool": "gofmt",
    "go.lintTool": "golangci-lint",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.organizeImports": "always"
    },
    "[go]": {
        "editor.defaultFormatter": "golang.go",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.organizeImports": "always"
        }
    }
}
  1. Install Go tools through VS Code:
    • Open the command palette (Cmd+Shift+P)
    • Type and select “Go: Install/Update Tools”
    • Check all tools and click “OK”

๐Ÿ”ถ Vim/Neovim + LazyVim setup

For Vim/Neovim users with LazyVim, the Go setup is straightforward:

  1. Ensure LazyVim is installed

  2. Add the Go language plugin to LazyVim configuration:

-- In lua/plugins/go.lua
return {
  {
    "ray-x/go.nvim",
    dependencies = {
      "ray-x/guihua.lua",
      "neovim/nvim-lspconfig",
      "nvim-treesitter/nvim-treesitter",
    },
    config = function()
      require("go").setup()
    end,
    event = { "CmdlineEnter" },
    ft = { "go", "gomod" },
    build = ':lua require("go.install").update_all_sync()'
  }
}
  1. Launch Neovim and run :checkhealth go to verify setup
  2. Make sure gopls and other Go tools are installed: :GoInstallBinaries

๐Ÿณ Container integration

Working with containers is essential for Go development, especially for microservices.

๐Ÿ“Ÿ DevContainer setup

DevContainers provide consistent development environments across team members.

  1. Create .devcontainer directory in the project root

  2. Create devcontainer.json:

{
  "name": "Go Development",
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "GO_VERSION": "1.24.0"
    }
  },
  "runArgs": [
    "--cap-add=SYS_PTRACE",
    "--security-opt", "seccomp=unconfined"
  ],
  "customizations": {
    "vscode": {
      "settings": {
        "go.toolsManagement.checkForUpdates": "local",
        "go.useLanguageServer": true,
        "go.gopath": "/go",
        "go.formatTool": "gofmt"
      },
      "extensions": [
        "golang.go",
        "GitHub.copilot",
        "ms-azuretools.vscode-docker"
      ]
    }
  },
  "remoteUser": "vscode",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "postCreateCommand": "go install github.com/go-delve/delve/cmd/dlv@latest"
}
  1. Create a Dockerfile for the DevContainer:
FROM mcr.microsoft.com/devcontainers/go:1-${GO_VERSION}-bullseye

# Install additional OS packages
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends \
    curl git bash-completion

USER vscode

# Install mise for version management
RUN curl https://mise.run | sh && \
    echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc

# Install Go tools
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest \
    && go install golang.org/x/tools/gopls@latest \
    && go install github.com/cosmtrek/air@latest

๐Ÿ”Œ Supporting mise in DevContainers

To integrate mise within a DevContainer:

  1. Modify the Dockerfile to include mise:
FROM mcr.microsoft.com/devcontainers/go:1-${GO_VERSION}-bullseye

# Install additional OS packages
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends \
    curl git bash-completion

USER vscode

# Install mise for version management
RUN curl https://mise.run | sh && \
    echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc

# Pre-install specific Go version with mise
RUN ~/.local/bin/mise install [email protected] && \
    ~/.local/bin/mise use --global [email protected]

# Install Go tools
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest \
    && go install golang.org/x/tools/gopls@latest \
    && go install github.com/cosmtrek/air@latest
  1. Ensure the project’s .mise.toml file is available within the container for automatic version switching

This setup allows the development container to respect project-specific Go versions defined in .mise.toml files, providing consistency across containerized and local development environments.

๐Ÿญ Multi-stage builds for production

For building production-ready container images:

# Build stage
FROM golang:1.24-bullseye AS build

WORKDIR /app

# Download dependencies first (better caching)
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download

# Copy source and build with cache
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -o /app/server .

# Runtime stage
FROM gcr.io/distroless/static:nonroot

WORKDIR /app

# Copy binary from build stage
COPY --from=build /app/server .

# Metadata
LABEL org.opencontainers.image.source="https://github.com/organization/project"
LABEL org.opencontainers.image.description="Go microservice"

EXPOSE 8080
ENTRYPOINT ["/app/server"]

๐Ÿ”ฅ Hot-reloading with Air

Air is a fantastic tool for live-reloading during development:

# Dockerfile.dev
FROM golang:1.24

WORKDIR /app

# Install Air
RUN go install github.com/cosmtrek/air@latest

# Copy and download dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Start Air for hot-reloading
CMD ["air", "-c", ".air.toml"]

Create .air.toml:

root = "."
tmp_dir = "tmp"

[build]
  cmd = "go build -o ./tmp/app ."
  bin = "./tmp/app"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor"]
  include_ext = ["go", "tpl", "tmpl", "html"]
  exclude_unchanged = true
  
[log]
  time = true

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

[misc]
  clean_on_exit = true
  # Set poll = true when using volume mounts for better performance
  poll = true

๐Ÿ”„ Maintenance and updates

Keeping Go installations and tools up-to-date is crucial for security and performance.

๐Ÿ”ผ Updating system Go

macOS

brew update
brew upgrade go

Ubuntu/Debian

sudo apt update
sudo apt upgrade golang-go

Fedora

sudo dnf upgrade golang

Red Hat UBI 9 / RHEL 9

sudo yum module update go-toolset

๐Ÿ†™ Updating version-managed Go

With mise:

mise upgrade go
mise install go@latest
mise use --global go@latest

๐Ÿ› ๏ธ Updating tools

For globally installed tools:

go install -u github.com/golangci/golangci-lint/cmd/golangci-lint@latest

For tool dependencies (Go 1.24+):

# Update tool dependencies in go.mod
go get -u -tool golang.org/x/vuln/cmd/govulncheck

๐Ÿ“ฆ Updating dependencies

To update all dependencies:

go get -u ./...
go mod tidy

To update a specific dependency:

go get -u github.com/example/package
go mod tidy

๐Ÿ”„ Switching between Go versions for different projects

One of the most powerful features of mise is automatic version switching. I’ve found this to be incredibly useful when working on multiple Go projects that require different versions.

๐Ÿค– Automatic version switching

With mise installed, simply create a .mise.toml file in each project directory:

# Project A (.mise.toml)
[tools]
go = "1.23.1"
# Project B (.mise.toml)
[tools]
go = "1.24.4"

When navigating between directories, mise automatically activates the correct Go version. No more manual PATH adjustments or remembering which version a project needs.

The shell integration in mise ensures that when you enter a directory with a .mise.toml, .tool-versions, or even a .go-version file, it will detect and switch the Go version automatically. When you leave the directory, it reverts to your global default.

This automatic detection and switching has been a game-changer for my workflow, especially when quickly moving between projects with different version requirements.

๐Ÿ‘‹ Manual version switching

For temporary work with a specific Go version:

To set a new global default:

mise use --global [email protected]

๐Ÿ”Œ VS Code integration

For complete VS Code integration with mise:

  1. Install the mise VS Code extension
  2. It will automatically respect .mise.toml configuration
  3. The Go extension will use the correct version for each project

๐Ÿง  Advanced patterns for microservices

For teams working on microservices with both frontend and backend components:

1๏ธโƒฃ Shared code approach

One of the patterns I’ve found most effective for microservices is the shared code approach, where common functionality is extracted into a reusable module.

Create a common module for code shared across services:

organization/
โ”œโ”€โ”€ common/           # Shared module
โ”‚   โ”œโ”€โ”€ go.mod
โ”‚   โ”œโ”€โ”€ auth/
โ”‚   โ”œโ”€โ”€ logging/
โ”‚   โ””โ”€โ”€ models/
โ”œโ”€โ”€ service-a/        # Microservice A
โ”‚   โ”œโ”€โ”€ go.mod        # Requires common module
โ”‚   โ””โ”€โ”€ main.go
โ””โ”€โ”€ service-b/        # Microservice B
    โ”œโ”€โ”€ go.mod        # Requires common module
    โ””โ”€โ”€ main.go

Implementation:

// In service-a/go.mod
module github.com/organization/service-a

go 1.24

require (
    github.com/organization/common v1.0.0
)

// For local development
replace github.com/organization/common => ../common

This pattern allows for code reuse while maintaining clear boundaries between services. During local development, the replace directive points to the local common module, while in production each service can depend on a specific published version.

2๏ธโƒฃ Multi-module monorepo with go.work

Go 1.18+ introduced workspaces, which provide an elegant solution for monorepos with multiple modules:

monorepo/
โ”œโ”€โ”€ go.work           # Workspace file
โ”œโ”€โ”€ shared/           # Shared libraries
โ”‚   โ”œโ”€โ”€ go.mod
โ”‚   โ””โ”€โ”€ util/
โ”œโ”€โ”€ service-a/        # Microservice A
โ”‚   โ”œโ”€โ”€ go.mod
โ”‚   โ””โ”€โ”€ main.go
โ””โ”€โ”€ service-b/        # Microservice B
    โ”œโ”€โ”€ go.mod
    โ””โ”€โ”€ main.go

Creating a go.work file:

# go.work file
go 1.24

use (
    ./shared
    ./service-a
    ./service-b
)

This approach has completely replaced the need for replace directives in each module’s go.mod. Now, when working locally, Go automatically knows to use the local versions of each module, making local development much smoother.

The workspace feature means monorepos no longer need complicated setups - the go.work file handles everything elegantly, and each service can maintain its own dependency graph.

For CI/CD, you can simply disable the workspace with GOWORK=off and the normal module resolution will take over.

3๏ธโƒฃ Multi-environment docker-compose

For local development of multiple services:

# docker-compose.yml
version: '3.8'
services:
  service-a:
    build:
      context: ./service-a
      dockerfile: Dockerfile.dev
    volumes:
      - ./service-a:/app:delegated
      - go-mod-cache:/go/pkg/mod
    ports:
      - "8080:8080"
    environment:
      - SERVICE_B_URL=http://service-b:8081
  
  service-b:
    build:
      context: ./service-b
      dockerfile: Dockerfile.dev
    volumes:
      - ./service-b:/app:delegated
      - go-mod-cache:/go/pkg/mod
    ports:
      - "8081:8081"

volumes:
  go-mod-cache:

๐Ÿ Conclusion: The cross-platform advantage

After implementing this setup across my projects on both macOS and Linux, I’ve found that the combination of a system Go installation with mise for version management offers the most intuitive, reliable, and modern approach to managing Go.

This setup provides:

  1. Simplicity: Easy to install and configure on any platform
  2. Reliability: Consistent environments across team members on different operating systems
  3. Portability: Works across development and production environments on both macOS and Linux
  4. Flexibility: Supports multiple Go versions and projects
  5. Performance: Optimized for both macOS and Linux
  6. Integration: Works well with modern IDEs and development tools

For my greenfield projects without backward compatibility concerns, this approach offers the perfect balance of developer experience and operational reliability. By following these recommendations, you can focus on building great microservices rather than fighting with your development environment.

Remember that the ultimate goal is a setup that gets out of your way and lets you focus on writing great Go code. This guide helps achieve exactly that with the most modern, efficient approach possible in 2025.

Golang lifecycle diagram showing the growth and management of Go projects, represented as plants in various stages of development

@usrbinkat on social

Mostly tech with fun and opinions to keep it spicy.