# The Go stack we would set up today

> Source: https://learn-go.org/tools/
> Part of Learn Go, free to read.

Go's best property as a toolchain is how little of it there is. `go build`, `go test`, `go vet` and `gofmt` ship with the language and cover more than most ecosystems manage with twenty packages.

:::note How this page is funded
Some links here are affiliate links, marked `sponsored`. If you buy through one we earn a commission at no cost to you. It does not buy placement — most of what we recommend below is free and has no affiliate programme.
:::

## Install these four

### `golangci-lint`

The one addition everyone should make. It bundles `staticcheck`, `errcheck`, `errorlint`, `gosec`, `contextcheck` and dozens more behind one fast command.

```bash
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run
```

The config we use is in [the failure-mode catalogue](/review/failure-modes/), and the rules that matter most are the error-handling and context ones.

### The race detector (already installed)

```bash
go test -race -count=1 ./...
```

Not optional. Concurrency is where generated Go is weakest and `-race` is the only thing that finds it reliably. `-count=1` disables test caching, which matters when an agent is iterating — a cached pass is not a pass.

### `goleak`

```bash
go get go.uber.org/goleak
```

```go
func TestMain(m *testing.M) { goleak.VerifyTestMain(m) }
```

Three lines, and every goroutine leak becomes a test failure instead of a slow memory climb in production.

### `air` or `wgo` for reload

Optional, and pleasant. Rebuilds on save so the feedback loop stays under two seconds.

## Editor

VS Code with `gopls` is free, excellent, and what most Go developers use. The extension is maintained by the Go team.

:::promo jetbrains
:::

The GoLand case is the debugger and the profiler integration. If you spend your days in a large Go service, stepping through generated concurrency code beats reading it, every time.

## Hosting

Go's deployment story is its other quiet advantage: a single static binary, no runtime, a `FROM scratch` container of a few megabytes.

:::promo hetzner
:::

For a Go binary, a plain VPS is often the right answer — there is no runtime to manage, so the operational overhead of a managed platform buys you less than it does for Python or Node.

:::promo digitalocean
:::

App Platform is still the least-effort path if you would rather not touch a server, and the $200 credit covers a good while of experimenting.

:::verdict Honest note
For a small Go service, Fly.io's free tier and Cloud Run's free tier both work well and we earn nothing from either. Start there if you are just deploying something to see it run.
:::

## Learning

:::promo boot-dev
:::

The strongest recommendation on this page. Boot.dev's Go track is project-based in a way that survives the agent era — you cannot paste your way through it, because the thing being taught is the reasoning about concurrency and memory.

:::promo manning
:::

For the reference-book shelf: Manning's Go titles go deeper on concurrency patterns than any tutorial, and concurrency is exactly where you need depth when reviewing generated code.

## What to skip

- **A dependency injection framework.** Go does not need one. Constructor functions and explicit wiring in `main` are clearer and are what the ecosystem expects.
- **A heavyweight web framework.** `net/http` plus the 1.22 routing improvements covers most services. Add `chi` if you want middleware composition. Anything larger is usually a mistake you notice a year later.
- **An ORM.** `sqlc` generates type-safe Go from your SQL and is a better fit for the language than an ORM is. `pgx` directly if you prefer.
- **A second linter.** `golangci-lint` already runs the ones you were going to add.
- **`GOPATH` advice from before 2019.** Modules replaced it. If a tutorial mentions `$GOPATH/src`, it is out of date.

## The whole setup

```yaml .golangci.yml
linters:
  enable: [errcheck, errorlint, govet, staticcheck, contextcheck, bodyclose, noctx, gosec]
```

```makefile Makefile
check: lint test
lint:
	gofmt -l -w . && golangci-lint run
test:
	go test -race -count=1 -timeout 60s ./...
```

That is the entire toolchain. Everything else is preference.

## Common questions

### Do I need `golangci-lint` if I already run `go vet`?

Yes — `go vet` is deliberately conservative and only reports things that are almost certainly bugs. `staticcheck` and `errorlint`, both bundled in `golangci-lint`, catch a much wider set, including the `%w` wrapping mistake that quietly breaks `errors.Is`.

### Is `-race` too slow to run every time?

It roughly doubles test time. Given that it finds the one class of bug Go's compiler cannot, that is a good trade — and if your suite is too slow for it, the suite is the problem.

### sqlc, an ORM, or raw SQL?

`sqlc` for most projects: you write SQL, it generates typed Go, and there is no query builder to fight. Raw `pgx` when you need dynamic queries. An ORM only if your team strongly prefers one.
