
GOPATH — a way of working that the language itself abandoned. We rewrote it as a hello world that actually runs on Ubuntu, with the current version and modules.This is the shortest path from a clean Ubuntu installation to your first Go program running. It takes about ten minutes, and you end up with a single binary that runs on any Linux system — no interpreter, no virtual machine, no dependencies to install on the server.
Why Go, in two sentences
Go was designed at Google in 2007 because C++ took too long to compile and concurrency was too much work. The result: it compiles in seconds, has concurrency built into the language, and produces a static binary. That is why Docker, Kubernetes, Terraform, and Prometheus are written in it. The full story is in The history of the Go language.
Installing on Ubuntu
There are three options. The official tarball is recommended — the package from apt usually lags one or two versions behind, and that matters in Go because the language releases every six months.
# descobre a versão atual sem abrir o navegador
VER=$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n1)
echo "$VER"
# baixa e instala no lugar canônico
cd /tmp
curl -fLO "https://go.dev/dl/${VER}.linux-amd64.tar.gz"
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "${VER}.linux-amd64.tar.gz"
Notice rm -rf /usr/local/go before extracting: it is the step the official documentation requires and almost every tutorial forgets. Extracting over an old installation mixes files from two versions and produces errors that are hard to diagnose.
Now for PATH. To apply it system-wide and make it work for new users:
echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh
source /etc/profile.d/go.sh
go version
If you see something like go version go1.27.1 linux/amd64, you are ready. If the command was not found, the source above fixes it for the current session — in future sessions it loads automatically.
On an ARM machine, such as a Raspberry Pi, replace linux-amd64 with linux-arm64.
The alternatives, and when to use them
# snap — versão recente, atualiza sozinho
sudo snap install go --classic
# apt — mais simples, versão mais velha
sudo apt install golang-go
apt works if you only need to compile someone else's software and do not care about the version. For development, stick with the tarball.
Hello, world
Before setting up a project structure, look at the bare language. Create ola.go:
package main
import "fmt"
func main() {
fmt.Println("Olá, mundo!")
}
go run ola.go
go run compiles and runs in memory without leaving a binary — it is the way to experiment. Three things to notice: every executable program starts with package main; the entry-point function is func main(); and an import is required and cannot be unused. If you import a package without using it, Go refuses to compile. It is deliberately strict.
Now the right way: a module
In practice, nobody works with standalone files. Go has organized projects into modules since version 1.11, and this is the change that retired the old GOPATH from this post in 2017:
mkdir ~/ola && cd ~/ola
go mod init exemplo.com/ola
This creates go.mod, which declares the module name and language version. Now for a slightly more interesting program — create main.go:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
fmt.Print("Qual o seu nome? ")
leitor := bufio.NewReader(os.Stdin)
nome, err := leitor.ReadString('n')
if err != nil {
fmt.Fprintln(os.Stderr, "erro ao ler a entrada:", err)
os.Exit(1)
}
nome = strings.TrimSpace(nome)
if nome == "" {
fmt.Println("Você não digitou nada.")
return
}
fmt.Printf("Olá, %s! Bem-vindo ao Go.n", nome)
}
go run .
Here is the language's signature feature: if err != nil. Go has no exceptions — every function that can fail returns an error along with its result, and you must decide what to do with it. It is verbose, it is criticized, and it is intentional: the error path is visible in the code, not hidden in a catch far away.
Building the binary
This is the part that sells the language to server administrators:
go build -o ola
ls -lh ola
./ola
The file ola is a complete executable. Copy it to another Linux system and it runs — no need to install Go there, or a runtime, or a library. You can also compile for another system without leaving your own:
GOOS=linux GOARCH=arm64 go build -o ola-arm # Raspberry Pi
GOOS=windows GOARCH=amd64 go build -o ola.exe # Windows
GOOS=darwin GOARCH=arm64 go build -o ola-mac # Mac com Apple Silicon
Cross-compilation with one environment variable, no extra toolchain. That is why so many modern command-line tools are distributed as one binary per platform.
Using a dependency
go get github.com/fatih/color
package main
import "github.com/fatih/color"
func main() {
color.Green("Olá, mundo!")
color.New(color.FgYellow, color.Bold).Println("agora em amarelo e negrito")
}
go mod tidy # ajusta o go.mod: adiciona o que falta, remove o que sobra
go run .
The go.sum file, created automatically, stores the hash of each dependency — ensuring that the version downloaded tomorrow is byte-for-byte identical to today's. Keep both files under version control, go.mod and go.sum, along with the code.
A taste of concurrency
You cannot introduce Go without showing the goroutine, which is why the language won over infrastructure:
package main
import (
"fmt"
"net/http"
"time"
)
func checar(url string, resultado chan<- string) {
inicio := time.Now()
resp, err := http.Get(url)
if err != nil {
resultado <- fmt.Sprintf("%-28s ERRO: %v", url, err)
return
}
defer resp.Body.Close()
resultado <- fmt.Sprintf("%-28s %s em %v", url, resp.Status, time.Since(inicio).Round(time.Millisecond))
}
func main() {
sites := []string{
"https://www.linuxpro.com.br",
"https://go.dev",
"https://kernel.org",
}
resultado := make(chan string)
for _, s := range sites {
go checar(s, resultado) // dispara os três ao mesmo tempo
}
for range sites {
fmt.Println(<-resultado) // recebe conforme forem chegando
}
}
go run .
The three sites are queried in parallel, and the total takes as long as the slowest, not the sum of all three. That takes one word — go — and one channel. No library, no thread pool, no callback.
The tools that come included
go fmt ./... # formata no padrão oficial; não há debate de estilo em Go
go vet ./... # aponta erro provável que compila mesmo assim
go test ./... # roda os testes
go doc fmt.Println # documentação sem sair do terminal
go clean -cache # limpa o cache de build quando algo fica estranho
go fmt deserves special mention: the language has one official style, enforced by a tool. From the outset, this ended the debate over tabs and braces that consumes time in other communities.
Multiple versions on the same machine
When a project is stuck on an older version, Go itself handles it — no external manager:
go install golang.org/dl/go1.26@latest
go1.26 download
go1.26 version
And since 1.21, there is something even better: if the go.mod for the project requires a version you do not have, GOTOOLCHAIN downloads the right one at build time, automatically.
Where to go next
- A Tour of Go — the official, interactive tutorial runs in your browser. It is the best starting point.
- Effective Go — how to write code that looks like Go, not translated Java.
- Here on the blog: The history of the Go language, the detailed installation howto and the main programming languages.
- Built in Go, for studying real code: go-postfixadmin and Gogs.
Ten minutes, a static binary, and a blue gopher. A good start.