The Go Developer's Toolkit: Essential Libraries, Tools, and Environments for Productivity
Building efficient, scalable applications with Go requires more than just understanding the language syntax. The most productive Go developers rely on a carefully selected toolkit of libraries, development tools, and environments to streamline their workflow. This guide explores the essential components of a modern Go developer’s toolkit, with practical examples and configuration tips to help you build better software faster.
Getting Started: Setting Up Your Go Environment
Before exploring specific tools and libraries, let’s ensure your Go development environment is properly configured. A solid foundation makes everything else more effective.
Installing Go
Installation varies by platform, but the process is straightforward:
macOS (using Homebrew):
brew install go
Ubuntu/Debian:
sudo apt update
sudo apt install golang-go
Windows: Download the installer from golang.org/dl and run it.
Configuring Your Environment
After installation, set up your environment variables. Add these to your .bashrc, .zshrc, or equivalent:
export GOPATH=$HOME/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
The GOPATH is where your Go code, packages, and compiled binaries live. While Go modules have reduced the importance of GOPATH, having it properly set up is still beneficial for certain tools.
Verifying Your Installation
Ensure everything is working by running:
go version
You should see output like go version go1.21.0 linux/amd64 (version and platform will vary).
Essential Tools for Modern Go Development
The following tools form the backbone of efficient Go development, helping with everything from dependency management to code quality.
Go Modules: Dependency Management
Go Modules is the official dependency management system for Go. It allows you to track dependencies, their versions, and ensures reproducible builds.
How to use:
Initialize a new module:
go mod init github.com/username/projectnameAdd dependencies automatically by importing and using them in your code, then run:
go mod tidyView your dependencies:
go list -m all
Your dependencies are tracked in go.mod, while go.sum ensures their integrity.
Modern usage tips:
- Use versioned imports in your code:
import "github.com/user/repo/v2" - Pin specific versions in your
go.modwith// indirectcomments - Use workspace mode for multi-module repositories with
go work
Code Quality Tools
1. golangci-lint
A fast, comprehensive linter that runs multiple linters concurrently.
Installation:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Basic usage:
golangci-lint run
Pro tip: Create a .golangci.yml configuration file to customize which linters run and their settings:
linters:
enable:
- gofmt
- goimports
- govet
- staticcheck
- revive
- errcheck
linters-settings:
errcheck:
check-type-assertions: true
issues:
exclude-rules:
- path: _test\.go
linters:
- errcheck
2. gofumpt
An enhanced version of gofmt with additional formatting rules for more consistent code.
Installation:
go install mvdan.cc/gofumpt@latest
Usage:
gofumpt -l -w .
3. staticcheck
A state-of-the-art static analysis tool for finding bugs and performance issues.
Installation:
go install honnef.co/go/tools/cmd/staticcheck@latest
Usage:
staticcheck ./...
Code Generation and Documentation
1. mockgen
Generate mock implementations of interfaces for testing.
Installation:
go install go.uber.org/mock/mockgen@latest
Usage:
mockgen -source=interfaces.go -destination=mock_interfaces.go -package=mocks
2. swag
Generate Swagger documentation from Go annotations.
Installation:
go install github.com/swaggo/swag/cmd/swag@latest
Usage:
swag init -g main.go
Example annotation:
// @title User Service API
// @version 1.0
// @description Service for managing users
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// CreateUser creates a new user
// @Summary Create a user
// @Description Create a new user in the system
// @Tags users
// @Accept json
// @Produce json
// @Param user body models.CreateUserRequest true "User information"
// @Success 201 {object} models.User
// @Failure 400 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Router /users [post]
func (c *Controller) CreateUser(w http.ResponseWriter, r *http.Request) {
// Implementation
}
3. go-bindata
Embeds assets (images, HTML, etc.) into your Go binary.
Installation:
go install github.com/kevinburke/go-bindata/go-bindata@latest
Usage:
go-bindata -o assets.go -pkg main assets/...
Development Environments and IDEs
Your choice of development environment significantly impacts productivity. Here are the top options for Go development:
1. Visual Studio Code with Go Extension
VSCode provides an excellent balance of features, performance, and customization.
Setup:
- Install Visual Studio Code
- Install the Go extension
- Open a Go file, and it will prompt you to install the necessary tools
Recommended settings:
{
"go.useLanguageServer": true,
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.formatTool": "gofumpt",
"go.testOnSave": true,
"go.coverOnSave": true,
"go.coverageDecorator": {
"type": "highlight",
"coveredHighlightColor": "rgba(64,128,128,0.5)",
"uncoveredHighlightColor": "rgba(128,64,64,0.25)"
},
"go.testFlags": ["-v"],
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
Key features:
- IntelliSense (autocompletion and parameter info)
- Code navigation
- Debugging support
- Integrated testing and coverage visualization
- Refactoring tools
2. GoLand
JetBrains’ dedicated Go IDE offers the most comprehensive feature set.
Key features:
- Deep language understanding
- Advanced refactoring tools
- Built-in database tools
- Docker and Kubernetes integration
- Advanced debugger
- Profiling tools integration
Pro tip: GoLand’s “File Watcher” can run linters and formatters automatically when files change:
- Go to
Settings/Preferences→Tools→File Watchers - Add a new watcher for
gofumptorgolangci-lint
3. Vim/Neovim with Go Plugins
For developers who prefer terminal-based workflows, Vim with the right plugins provides an efficient experience.
Basic setup using vim-plug:
call plug#begin()
Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'preservim/tagbar'
call plug#end()
" Go syntax highlighting
let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1
let g:go_highlight_types = 1
" Auto formatting and importing
let g:go_fmt_autosave = 1
let g:go_fmt_command = "goimports"
" Status line types/signatures
let g:go_auto_type_info = 1
" Run :GoBuild or :GoTestCompile based on the go file
function! s:build_go_files()
let l:file = expand('%')
if l:file =~# '^\f\+_test\.go$'
call go#test#Test(0, 1)
elseif l:file =~# '^\f\+\.go$'
call go#cmd#Build(0)
endif
endfunction
autocmd FileType go nmap <leader>b :<C-u>call <SID>build_go_files()<CR>
autocmd FileType go nmap <leader>r <Plug>(go-run)
autocmd FileType go nmap <leader>t <Plug>(go-test)
Essential Libraries for Go Development
The following libraries address common needs in Go applications and help you avoid reinventing the wheel.
Web Development
1. gorilla/mux - HTTP Router
Powerful URL router and dispatcher for matching URL paths to handlers.
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// Route handlers
r.HandleFunc("/", homeHandler)
r.HandleFunc("/products", productsHandler)
r.HandleFunc("/products/{id:[0-9]+}", productHandler)
// Serve static files
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
// Middleware
r.Use(loggingMiddleware)
// Start server
http.ListenAndServe(":8080", r)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the home page!")
}
func productsHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Products page")
}
func productHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID := vars["id"]
fmt.Fprintf(w, "Product %s", productID)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RequestURI)
next.ServeHTTP(w, r)
})
}
2. chi - Lightweight, composable router
An alternative to gorilla/mux, chi is lightweight and composable.
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// Middleware stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Routes
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome"))
})
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers)
r.Post("/", createUser)
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
})
})
http.ListenAndServe(":8080", r)
}
func listUsers(w http.ResponseWriter, r *http.Request) {
// Implementation
}
func createUser(w http.ResponseWriter, r *http.Request) {
// Implementation
}
func getUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
// Implementation using userID
}
func updateUser(w http.ResponseWriter, r *http.Request) {
// Implementation
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
// Implementation
}
3. gin - Web framework
A fast, minimalist web framework with features like routing, middleware, and rendering.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
r := gin.Default() // Includes Logger and Recovery middleware
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
// Group routes
api := r.Group("/api")
{
api.GET("/users", getUsers)
api.GET("/users/:id", getUserByID)
api.POST("/users", createUser)
}
r.Run(":8080")
}
func getUsers(c *gin.Context) {
users := []User{
{ID: "1", Name: "John Doe", Email: "john@example.com"},
{ID: "2", Name: "Jane Smith", Email: "jane@example.com"},
}
c.JSON(http.StatusOK, users)
}
func getUserByID(c *gin.Context) {
id := c.Param("id")
// In a real app, fetch from database
user := User{ID: id, Name: "John Doe", Email: "john@example.com"}
c.JSON(http.StatusOK, user)
}
func createUser(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// In a real app, save to database
c.JSON(http.StatusCreated, newUser)
}
HTTP Clients
1. resty - Simple HTTP client
An expressive, simple HTTP client with a fluent API.
package main
import (
"fmt"
"github.com/go-resty/resty/v2"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
client := resty.New()
// Set common headers, base URL, etc.
client.SetHeader("Content-Type", "application/json")
client.SetBaseURL("https://api.example.com")
client.SetTimeout(10 * time.Second)
// GET request
resp, err := client.R().
SetQueryParams(map[string]string{
"page": "1",
"limit": "10",
}).
Get("/users")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Status:", resp.Status())
fmt.Println("Body:", resp.String())
// POST request with JSON
user := &User{
Name: "John Doe",
Email: "john@example.com",
}
resp, err = client.R().
SetBody(user).
SetResult(&User{}). // Unmarshal response into User struct
Post("/users")
if err != nil {
fmt.Println("Error:", err)
return
}
createdUser := resp.Result().(*User)
fmt.Println("Created user:", createdUser.ID, createdUser.Name)
}
Database Access
1. sqlx - Enhanced database access
An extension of the standard database/sql package with additional functionality.
package main
import (
"fmt"
"log"
_ "github.com/lib/pq"
"github.com/jmoiron/sqlx"
)
type User struct {
ID int `db:"id"`
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
Email string `db:"email"`
}
func main() {
// Connect to database
db, err := sqlx.Connect("postgres", "user=postgres dbname=testdb sslmode=disable")
if err != nil {
log.Fatalln(err)
}
// Create schema
schema := `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
first_name TEXT,
last_name TEXT,
email TEXT UNIQUE
);`
db.MustExec(schema)
// Insert data using named parameters
tx := db.MustBegin()
tx.MustExec("INSERT INTO users (first_name, last_name, email) VALUES ($1, $2, $3)",
"John", "Doe", "john@example.com")
tx.NamedExec("INSERT INTO users (first_name, last_name, email) VALUES (:first, :last, :email)",
map[string]interface{}{
"first": "Jane",
"last": "Smith",
"email": "jane@example.com",
})
tx.Commit()
// Query data
users := []User{}
err = db.Select(&users, "SELECT * FROM users")
if err != nil {
log.Fatalln(err)
}
for _, user := range users {
fmt.Printf("%d: %s %s (%s)\n", user.ID, user.FirstName, user.LastName, user.Email)
}
// Get a single record
var user User
err = db.Get(&user, "SELECT * FROM users WHERE id=$1", 1)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("User 1: %s %s\n", user.FirstName, user.LastName)
}
2. gorm - Full-featured ORM
A developer-friendly ORM with full-featured functionality.
package main
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=testdb port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalln("Failed to connect to database:", err)
}
// Auto Migrate
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100})
// Read
var product Product
db.First(&product, 1) // Find product with ID = 1
fmt.Println("Product 1:", product.Code, product.Price)
db.First(&product, "code = ?", "D42") // Find product with code = D42
fmt.Println("Product D42:", product.ID, product.Price)
// Update
db.Model(&product).Updates(Product{Price: 200, Code: "D42"}) // Update non-zero fields
// Delete
db.Delete(&product, 1) // Delete product with ID = 1
}
Logging and Observability
1. zap - Fast, structured logger
A fast, structured logger with leveled logging and sampling.
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"time"
)
func main() {
// Production logger with JSON formatting
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Production logger initialized")
// Log with structured fields
logger.Info("User logged in",
zap.String("username", "john_doe"),
zap.String("ip", "192.168.1.1"),
zap.Duration("latency", time.Millisecond*53),
)
// Custom logger configuration
config := zap.NewProductionConfig()
config.OutputPaths = []string{"stdout", "/var/log/myapp.log"}
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
customLogger, _ := config.Build()
defer customLogger.Sync()
customLogger.Error("Failed login attempt",
zap.String("username", "unknown"),
zap.String("ip", "10.0.0.1"),
zap.Int("attempts", 5),
)
// Create a sugared logger (slightly slower but more convenient API)
sugar := logger.Sugar()
sugar.Infow("Sugared logger", "key", "value", "count", 42)
sugar.Infof("User %s logged in from %s", "jane_doe", "10.0.0.2")
}
2. prometheus client - Metrics collection
Collect and expose metrics for Prometheus monitoring.
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status"},
)
httpDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint"},
)
)
func main() {
// Register metrics handler
http.Handle("/metrics", promhttp.Handler())
// API endpoints
http.HandleFunc("/api/users", metricsMiddleware(usersHandler))
http.HandleFunc("/api/products", metricsMiddleware(productsHandler))
http.ListenAndServe(":8080", nil)
}
func metricsMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create a custom ResponseWriter to capture status code
ww := newStatusResponseWriter(w)
// Call the handler
next(ww, r)
// Record metrics
duration := time.Since(start).Seconds()
status := http.StatusText(ww.status)
httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, status).Inc()
httpDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
}
}
// A ResponseWriter that tracks status code
type statusResponseWriter struct {
http.ResponseWriter
status int
}
func newStatusResponseWriter(w http.ResponseWriter) *statusResponseWriter {
return &statusResponseWriter{w, http.StatusOK}
}
func (w *statusResponseWriter) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code)
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
// Implementation
}
func productsHandler(w http.ResponseWriter, r *http.Request) {
// Implementation
}
Testing Tools
1. testify - Test assertions and mocks
Provides a toolkit for assertions, mocks, and suites in tests.
package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCalculator(t *testing.T) {
// Basic assertions
assert.Equal(t, 4, Add(2, 2), "2+2 should equal 4")
assert.NotEqual(t, 5, Add(2, 2), "2+2 should not equal 5")
// Assertions that stop the test on failure
require.Equal(t, 4, Add(2, 2), "2+2 must equal 4")
// Testing for errors
result, err := Divide(10, 2)
assert.NoError(t, err)
assert.Equal(t, 5, result)
// Expected errors
_, err = Divide(10, 0)
assert.Error(t, err)
assert.Equal(t, "division by zero", err.Error())
// Checking types
assert.IsType(t, "", GetString())
// Slices and maps
assert.Contains(t, []string{"a", "b", "c"}, "b")
assert.Subset(t, []int{1, 2, 3, 4}, []int{2, 4})
assert.ElementsMatch(t, []int{1, 2, 3}, []int{3, 1, 2})
}
// Function implementations for the test
func Add(a, b int) int {
return a + b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func GetString() string {
return "test"
}
2. gomock - Mocking framework
Creating mock implementations of interfaces for testing.
//go:generate mockgen -destination=mocks/mock_repository.go -package=mocks github.com/myuser/myapp/repository UserRepository
package service
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/myuser/myapp/models"
"github.com/myuser/myapp/mocks"
"github.com/stretchr/testify/assert"
)
func TestUserService_GetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// Create mock repository
mockRepo := mocks.NewMockUserRepository(ctrl)
// Set expectations
mockRepo.EXPECT().
GetByID(gomock.Any(), int64(123)).
Return(&models.User{
ID: 123,
Name: "John Doe",
Email: "john@example.com",
}, nil)
// Create service with mock repository
service := NewUserService(mockRepo)
// Call the method we want to test
user, err := service.GetUser(context.Background(), 123)
// Assert expectations
assert.NoError(t, err)
assert.NotNil(t, user)
assert.Equal(t, int64(123), user.ID)
assert.Equal(t, "John Doe", user.Name)
}
Development Workflow Enhancers
These tools help streamline your Go development workflow for maximum productivity.
Hot Reloading with air
air provides live reloading of your Go applications during development.
Installation:
go install github.com/cosmtrek/air@latest
Usage:
Create a
.air.tomlconfiguration file:air initRun your app with hot reloading:
air
Sample .air.toml configuration:
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/main ."
bin = "tmp/main"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test\\.go"]
exclude_unchanged = true
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
Pre-commit Hooks with pre-commit
Using pre-commit hooks ensures code quality before committing changes.
Installation:
pip install pre-commit
Create a .pre-commit-config.yaml file:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/dnephin/pre-commit-golang
rev: v0.5.1
hooks:
- id: go-fmt
- id: go-imports
- id: go-mod-tidy
- id: go-build
- id: go-test
- repo: local
hooks:
- id: golangci-lint
name: golangci-lint
description: Fast linters runner for Go
entry: golangci-lint run
types: [go]
language: system
pass_filenames: false
Install the hooks:
pre-commit install
Makefile for Common Tasks
Creating a Makefile simplifies common development tasks.
.PHONY: all build test clean lint run docker-build docker-run
# Default variables
APP_NAME=myapp
BUILD_DIR=build
MAIN_FILE=cmd/main.go
# Go build flags
LDFLAGS=-ldflags "-s -w"
all: clean lint test build
build:
@echo "Building $(APP_NAME)..."
@mkdir -p $(BUILD_DIR)
@go build $(LDFLAGS) -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_FILE)
test:
@echo "Running tests..."
@go test -v -race ./...
clean:
@echo "Cleaning..."
@rm -rf $(BUILD_DIR)
@go clean
lint:
@echo "Linting..."
@golangci-lint run
run: build
@echo "Running $(APP_NAME)..."
@./$(BUILD_DIR)/$(APP_NAME)
docker-build:
@echo "Building Docker image..."
@docker build -t $(APP_NAME):latest .
docker-run: docker-build
@echo "Running Docker container..."
@docker run --rm -p 8080:8080 $(APP_NAME):latest
# Database migrations
migrate-up:
@echo "Running migrations up..."
@migrate -path ./migrations -database "postgres://postgres:password@localhost:5432/mydb?sslmode=disable" up
migrate-down:
@echo "Running migrations down..."
@migrate -path ./migrations -database "postgres://postgres:password@localhost:5432/mydb?sslmode=disable" down
# Generate code (mocks, protobuf, etc.)
generate:
@echo "Generating code..."
@go generate ./...
# Run with hot reload
dev:
@air
Docker and Containerization
Containerizing Go applications ensures consistent environments from development to production.
Dockerfile Best Practices
Multistage build for minimal image size:
# Build stage
FROM golang:1.21-alpine AS builder
# Set working directory
WORKDIR /app
# Download dependencies first (for better caching)
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-s -w" -o app .
# Final stage
FROM alpine:3.18
# Add certificates for HTTPS
RUN apk --no-cache add ca-certificates && update-ca-certificates
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Set working directory
WORKDIR /app
# Copy the binary from builder
COPY --from=builder /app/app .
# Use non-root user
USER appuser
# Expose port
EXPOSE 8080
# Command to run
CMD ["./app"]
Docker Compose for Development
Create a docker-compose.yml for local development:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- go-modules:/go/pkg/mod
ports:
- "8080:8080"
depends_on:
- postgres
environment:
- DB_HOST=postgres
- DB_USER=postgres
- DB_PASSWORD=postgres
- DB_NAME=testdb
- DB_PORT=5432
- ENV=development
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=testdb
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
go-modules:
postgres-data:
Development Dockerfile (Dockerfile.dev):
FROM golang:1.21
WORKDIR /app
# Install air for hot reloading
RUN go install github.com/cosmtrek/air@latest
# Copy air config
COPY .air.toml .
# Install dependencies
COPY go.mod go.sum ./
RUN go mod download
# Command to run
CMD ["air", "-c", ".air.toml"]
CI/CD Pipeline Setup
A well-configured CI/CD pipeline ensures code quality and simplifies deployment.
GitHub Actions Workflow
Create a .github/workflows/go.yml file:
name: Go
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
cache: true
- name: Install dependencies
run: go mod download
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
- name: Test
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.out
- name: Build
run: go build -v ./...
docker:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: username/appname:latest,username/appname:${{ github.sha }}
cache-from: type=registry,ref=username/appname:buildcache
cache-to: type=registry,ref=username/appname:buildcache,mode=max
Conclusion: Building Your Optimal Go Toolkit
The Go ecosystem provides a wealth of tools and libraries to enhance your development experience. The optimal toolkit, however, depends on your specific needs and preferences.
Start with the essentials:
- Proper environment setup: Go Modules for dependency management
- Code quality tools: golangci-lint and gofumpt
- An IDE that suits your workflow: VSCode, GoLand, or Vim
- Core libraries for common tasks: web frameworks, database access, logging
- Testing tools for robust test suites
Then gradually incorporate more specialized tools as your needs evolve. Remember that the Go philosophy emphasizes simplicity and maintainability, so resist the urge to include dependencies that don’t provide significant value.
By thoughtfully curating your Go toolkit, you’ll spend less time fighting your tools and more time building high-quality, maintainable software that solves real problems.