Skip to content

Instantly share code, notes, and snippets.

@IceWreck
Created March 14, 2026 17:42
Show Gist options
  • Select an option

  • Save IceWreck/8d4722db8cbd43ee4d4adf506736617b to your computer and use it in GitHub Desktop.

Select an option

Save IceWreck/8d4722db8cbd43ee4d4adf506736617b to your computer and use it in GitHub Desktop.
Go Boilerplate
# Go Boilerplate
### Init
In the empty git repo, init with `go mod init github.com/username/projectname`.
The repo tree should look something like this.
```
.
├── AGENTS.md
├── Dockerfile
├── Makefile
├── README.md
├── bin
├── cmd
│ └── appname
│ └── main.go
├── config
│ └── example.env
├── go.mod
├── go.sum
└── pkg
├── config
│ ├── config.go
│ ├── constants.go
│ └── version.go
└── logger
└── logger.go
```
### Agents MD
`AGENTS.md` file also symlink to `CLAUDE.md`.
```markdown
# Agents Knowledge
## Architecture
TODO
## Coding Conventions
- Use the stdlib's slog package for logging.
- The log message should always start with lowercase and should not end with a period. Example: `slog.Info("info message")`
- Use early returns to reduce indentation.
- Extract complex logic into functions.
- Leverage data structures instead of deeply nested conditions.
- Write self-explanatory code – Prefer clear variable and function names over comments.
- Explain "why," not "what" – Comments should clarify intent, not restate code.
- Avoid redundant comments – Don't comment obvious things.
- Use comments for complex logic – Explain non-trivial decisions or workarounds.
- Write package, type, function, and method comments as full sentences; start with the name being described.
- Avoid premature abstractions; add interfaces or patterns only when they clarify behavior.
- While writing Go, accept interfaces and return types unless there is a clear reason not to do so.
## Formatting, Linting & Building
After significant code changes, format, lint and vet with:
```
make check
```
Build with:
```
make build
```
```
### Makefile
```makefile
#!make
-include .env
export $(shell test -f .env && sed 's/=.*//' .env)
SHELL := /bin/bash
IMAGE ?= docker.io/username/appname
VERSION_PKG := github.com/username/appname/pkg/config
GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null || true)
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || true)
BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -s -w -X $(VERSION_PKG).gitTag=$(GIT_TAG) -X $(VERSION_PKG).gitSha=$(GIT_SHA) -X $(VERSION_PKG).buildDate=$(BUILD_DATE)
.PHONY: *
run:
go run ./cmd/appname
build:
touch .env && go build -v -ldflags "$(LDFLAGS)" -o ./bin/appname ./cmd/appname
fmt:
go fmt ./...
goimports -w .
vet:
go vet ./...
clean:
rm -rf ./bin
deps:
go mod download
go mod tidy
deps-update:
go get -u ./...
go mod tidy
tools:
go install golang.org/x/tools/cmd/goimports@latest
check: fmt vet
container-build:
touch .env && podman build --build-arg GIT_TAG="$(GIT_TAG)" --build-arg GIT_SHA="$(GIT_SHA)" --build-arg BUILD_DATE="$(BUILD_DATE)" -t $(IMAGE) .
container-push:
podman push $(IMAGE)
```
### Main
Just a hello world using the logger and urfave/cli for CLI creation.
It should load config and then log it.
```go
package main
import (
"context"
"log/slog"
"os"
"github.com/username/appname/pkg/config"
"github.com/username/appname/pkg/logger"
"github.com/urfave/cli/v3"
)
func main() {
logger.SetupLogging()
cmd := &cli.Command{
Name: "appname",
Usage: "load config and print it",
Version: config.Version,
Action: run,
}
if err := cmd.Run(context.Background(), os.Args); err != nil {
slog.Error("command failed", slog.Any("err", err))
os.Exit(1)
}
}
func run(_ context.Context, _ *cli.Command) error {
cfg, err := config.Load()
if err != nil {
return err
}
slog.Info("loaded config", slog.Any("config", cfg))
return nil
}
```
### Config
```go
package config
import (
"github.com/caarlos0/env/v11"
)
type Config struct {
Server struct {
Port string `env:"PORT" envDefault:"8080"`
Host string `env:"HOST" envDefault:"0.0.0.0"`
}
}
func Load() (*Config, error) {
var cfg Config
if err := env.Parse(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
```
### Version
```go
package config
import (
"fmt"
"strings"
)
var (
gitTag string
gitSha string
buildDate string
)
// Version holds the version string, with tag and git sha info.
// Examples:
// dev
// v0.2.0 (5b84188, 2026-03-14T17:00:00Z)
// v0.3.2-SNAPSHOT (715f552, 2026-03-14T17:00:00Z)
// master (9ed35cb, 2026-03-14T17:00:00Z)
var Version = func() string {
if gitSha == "" {
return "dev"
}
gitTag = strings.TrimPrefix(gitTag, "v")
if buildDate == "" {
return fmt.Sprintf("%s (%s)", gitTag, gitSha)
}
return fmt.Sprintf("%s (%s, %s)", gitTag, gitSha, buildDate)
}()
```
### Logger
```go
package logger
import (
"log/slog"
"os"
"strings"
)
// SetupLogging configures the global slog logger with sensible defaults.
func SetupLogging() {
logLevel := os.Getenv("LOG_LEVEL")
logFormat := os.Getenv("LOG_FORMAT")
level := slog.LevelInfo
if logLevel != "" {
if err := level.UnmarshalText([]byte(strings.ToLower(logLevel))); err != nil {
level = slog.LevelInfo
}
}
options := &slog.HandlerOptions{Level: level}
var handler slog.Handler
switch {
case logFormat == "", strings.EqualFold(logFormat, "text"):
handler = slog.NewTextHandler(os.Stdout, options)
case strings.EqualFold(logFormat, "json"):
handler = slog.NewJSONHandler(os.Stdout, options)
default:
handler = slog.NewTextHandler(os.Stdout, options)
}
slog.SetDefault(slog.New(handler))
}
```
### Gitignore
```
# Binaries
/bin
*.exe
*.dll
*.so
*.dylib
# Environment files
.env
.env.*
# Build
/build
/dist
# IDE
.vscode/
.idea/
# OS
.DS_Store
# Other
/drafts
/data
```
### Dockerfile
```dockerfile
FROM golang:1.24.4 AS builder
WORKDIR /src
ARG GIT_TAG
ARG GIT_SHA
ARG BUILD_DATE
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux make build GIT_TAG="${GIT_TAG}" GIT_SHA="${GIT_SHA}" BUILD_DATE="${BUILD_DATE}"
FROM scratch
COPY --from=builder /src/bin/appname /appname
ENTRYPOINT ["/appname"]
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment