๐ง Golang installation and management in 2025 ๐
Posted on May 12, 2025 • 15 minutes • 2998 words
Table of contents
- ๐ Table of Contents
- ๐ก Bottom Line
- ๐ Introduction: Why Go installation matters
- ๐ ๏ธ Installation options
- ๐น Golang & mise installation
- ๐ Dependency management best practices
- ๐ Go workspace configuration
- ๐ป IDE and editor setup
- ๐ณ Container integration
- ๐ Maintenance and updates
- ๐ Switching between Go versions for different projects
- ๐ง Advanced patterns for microservices
- ๐ Conclusion: The cross-platform advantage
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.
๐ Table of Contents
- Bottom Line
- Introduction: Why Go installation matters
- Installation options
- Golang & mise installation
- Dependency management best practices
- Go workspace configuration
- IDE and editor setup
- Container integration
- Maintenance and updates
- Switching between Go versions for different projects
- Advanced patterns for microservices
- Conclusion: The cross-platform advantage
๐ก 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:
- mise (formerly rtx): Modern Rust-based cross-language version manager
- asdf with golang plugin : Mature cross-language manager with rich plugin ecosystem
- g (formerly goup): Lightweight Go-specific version manager
- gvm : Full-featured Go-specific version manager with environment isolation
- 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
- Simplicity: Easy to set up and understand
- Performance: Mise directly modifies PATH instead of using shims, providing better performance
- Multiple languages: Supports other languages the team might use (Node.js, Python, etc.)
- Container compatibility: Works well with Docker and devcontainers
- Active development: Regular updates and improvements
- 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
Enable module mode (default in Go 1.24+):
go env -w GO111MODULE=on
Use the Go module proxy for better security and reliability:
go env -w GOPROXY=https://proxy.golang.org,direct
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 )
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.
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
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"
}
}
}
- 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:
Ensure LazyVim is installed
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()'
}
}
- Launch Neovim and run
:checkhealth go
to verify setup - 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.
Create
.devcontainer
directory in the project rootCreate
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"
}
- 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:
- 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
- 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:
mise use [email protected]
To set a new global default:
mise use --global [email protected]
๐ VS Code integration
For complete VS Code integration with mise:
- Install the mise VS Code extension
- It will automatically respect
.mise.toml
configuration - 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:
- Simplicity: Easy to install and configure on any platform
- Reliability: Consistent environments across team members on different operating systems
- Portability: Works across development and production environments on both macOS and Linux
- Flexibility: Supports multiple Go versions and projects
- Performance: Optimized for both macOS and Linux
- 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.