---
title: "Air em Go: Hot Reload para Desenvolver APIs Mais Rápido"
url: "https://golang.com.br/blog/air-go-hot-reload-desenvolvimento/"
markdown_url: "https://golang.com.br/blog/air-go-hot-reload-desenvolvimento.MD"
description: "Aprenda a configurar Air em Go para recompilar APIs automaticamente: instalação, .air.toml, Docker Compose, Templ, testes e solução de erros."
date: "2026-08-28"
author: "Golang Brasil"
---

# Air em Go: Hot Reload para Desenvolver APIs Mais Rápido

Aprenda a configurar Air em Go para recompilar APIs automaticamente: instalação, .air.toml, Docker Compose, Templ, testes e solução de erros.


**Resposta rápida:** o **Air** observa seu projeto Go, recompila o código e reinicia a aplicação automaticamente a cada alteração válida. Para uma API local, o começo mais simples é instalar `github.com/air-verse/air`, executar `air init` para gerar o arquivo `.air.toml` e trocar o comando manual `go run ./cmd/api` por `air`. Em equipes, prefira registrar a ferramenta no próprio módulo com `go get -tool`, configurar explicitamente `build.cmd` e `build.entrypoint` e manter a pasta de binários temporários fora do Git.

Go compila rápido, mas o ciclo “salvar, encerrar o processo, executar de novo e voltar ao navegador” fica cansativo durante o desenvolvimento de APIs. Air automatiza justamente essa parte. Ele não muda a forma como seu programa funciona e não adiciona dependência ao binário final: é uma ferramenta externa usada apenas no ambiente local.

Neste guia, você vai configurar Air em uma API Go, entender o `.air.toml`, integrar geração de código com Templ, rodar o fluxo em Docker Compose e diagnosticar os problemas mais comuns.

## O que Air faz — e o que ele não faz

O fluxo básico do Air é:

1. observar arquivos e diretórios definidos na configuração;
2. esperar um pequeno intervalo para agrupar alterações consecutivas;
3. encerrar o processo anterior;
4. executar o comando de build;
5. iniciar o novo binário se a compilação terminar sem erro;
6. continuar observando o projeto.

Apesar de “hot reload” ser o termo mais buscado, Air normalmente faz um **rebuild seguido de restart**. Ele não injeta uma função nova dentro do processo em execução nem preserva memória, conexões ou goroutines do binário anterior.

Essa diferença importa. Se sua API mantém estado apenas em memória, esse estado desaparece a cada alteração. Banco de dados, Redis, arquivos e outros serviços externos continuam disponíveis, mas conexões são fechadas e abertas de novo. Seu programa deve inicializar e encerrar de forma previsível, inclusive com [graceful shutdown em Go](/blog/graceful-shutdown-go-producao/).

Air também não substitui:

- `go test` para validar comportamento;
- `go vet`, Staticcheck ou golangci-lint para análise;
- Delve para debugging interativo;
- Docker Compose para subir dependências;
- um supervisor ou orquestrador em produção.

Pense nele como uma automação do loop local, não como parte da arquitetura da aplicação.

## Air, go run, Makefile ou Docker Compose?

Cada opção resolve uma camada diferente:

| Ferramenta | Responsabilidade principal | Quando usar |
|---|---|---|
| `go run ./cmd/api` | Compilar e executar uma vez | Teste rápido ou projeto pequeno |
| Makefile | Dar nomes estáveis a comandos | Padronizar build, teste e geração |
| Air | Observar, recompilar e reiniciar | Desenvolvimento iterativo |
| Docker Compose | Coordenar API e dependências | PostgreSQL, Redis, filas e ambiente reproduzível |
| Delve | Pausar e inspecionar execução | Investigar estado e fluxo do programa |

Você pode combinar as ferramentas. Um Makefile pode oferecer `make dev`, que executa Air; o Docker Compose pode iniciar um container que roda o mesmo comando; e Delve pode aparecer como `build.entrypoint` em uma configuração separada.

Se você ainda está montando seu primeiro serviço, comece pelo tutorial de [API REST em Go](/aprenda/api-rest-go/) e volte a este artigo quando já conseguir executar a aplicação manualmente.

## Como instalar Air

O repositório atual é `github.com/air-verse/air`. Tutoriais antigos podem mostrar o namespace `cosmtrek/air`; prefira o caminho atual para instalar a ferramenta.

### Instalação global

Com Go 1.25 ou mais recente:

```bash
go install github.com/air-verse/air@latest
```

Garanta que o diretório de binários do Go está no `PATH`:

```bash
export PATH="$PATH:$(go env GOPATH)/bin"
air -v
```

A instalação global é prática para uso individual. O ponto fraco é que duas pessoas da equipe podem ter versões diferentes sem perceber.

### Instalação vinculada ao projeto

A partir do Go 1.25, o toolchain permite registrar ferramentas no módulo:

```bash
go get -tool github.com/air-verse/air@latest
go tool air -v
```

Depois, o comando de desenvolvimento pode ser:

```bash
go tool air
```

Essa abordagem deixa a dependência de ferramenta associada ao projeto e facilita reproduzir o ambiente em CI ou no onboarding. Depois de escolher uma versão, revise e faça commit das alterações em `go.mod` e `go.sum`, como faria com qualquer ferramenta compartilhada. Para entender melhor o arquivo do módulo, consulte [Go Modules na prática](/aprenda/go-modules-na-pratica/).

## Exemplo de API para usar com Air

Considere esta estrutura:

```text
minha-api/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
├── go.mod
└── .air.toml
```

O servidor mínimo pode ser:

```go
package main

import (
	"log/slog"
	"net/http"
	"os"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(`{"status":"ok"}`))
	})

	server := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	slog.Info("servidor iniciado", "addr", server.Addr)
	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		slog.Error("servidor encerrou com erro", "error", err)
		os.Exit(1)
	}
}
```

Confirme primeiro que o projeto funciona sem watcher:

```bash
go run ./cmd/api
curl http://localhost:8080/health
```

Só adicione Air depois desse teste. Isso separa erros da aplicação de erros da ferramenta.

## Gerando e ajustando o .air.toml

Na raiz do projeto, execute:

```bash
air init
```

Ou, se Air foi registrado como tool do módulo:

```bash
go tool air init
```

O comando gera uma configuração completa. Para uma API em `cmd/api`, uma versão enxuta pode ser:

```toml
root = "."
tmp_dir = "tmp"

[build]
cmd = "go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api"]
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
delay = 300
stop_on_error = true
send_interrupt = true
kill_delay = "500ms"

[log]
time = true

[misc]
clean_on_exit = true
```

Os campos mais importantes são:

- **`build.cmd`**: comando executado para produzir o programa;
- **`build.entrypoint`**: binário que Air deve iniciar depois do build;
- **`include_ext`**: extensões que podem disparar o ciclo;
- **`exclude_dir`**: diretórios que não devem ser observados;
- **`delay`**: debounce em milissegundos;
- **`stop_on_error`**: evita iniciar um binário inválido quando o build falha;
- **`send_interrupt`**: envia interrupção antes de matar o processo;
- **`kill_delay`**: dá tempo para o encerramento terminar;
- **`clean_on_exit`**: remove temporários quando Air é encerrado.

O campo legado `build.bin` aparece em exemplos antigos, mas a documentação atual recomenda **`build.entrypoint`**. Usar a forma nova também permite passar argumentos padrão:

```toml
[build]
cmd = "go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api", "serve", "--addr=:8080"]
```

Adicione a pasta temporária ao `.gitignore`:

```gitignore
/tmp/
```

Não exclua todo diretório chamado `tmp` indiscriminadamente se sua aplicação guarda fixtures ou arquivos relevantes nele. O nome e o caminho devem refletir a estrutura real do projeto.

## Executando o ciclo de desenvolvimento

Com a configuração pronta:

```bash
air
```

Ou:

```bash
go tool air
```

Altere a resposta do endpoint e salve o arquivo. Air deve mostrar o build, interromper o processo anterior e iniciar o novo binário.

Você também pode passar argumentos para a aplicação depois de `--`:

```bash
air -- serve --addr=:8081
```

Quando houver uma configuração específica:

```bash
air -c .air.dev.toml -- serve
```

Para investigar por que um arquivo não disparou rebuild, rode o modo de debug:

```bash
air -d
```

## Rodar testes antes de reiniciar: vale a pena?

É possível transformar o comando de build em uma pequena pipeline:

```toml
[build]
cmd = "go test ./... && go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api"]
```

Isso garante que a aplicação só reinicie quando toda a suíte passar. Em um projeto pequeno, o feedback é ótimo. Em um monorepo grande, executar todos os testes a cada tecla salva pode deixar o ciclo lento.

Uma divisão mais equilibrada é:

- Air executa `go build` a cada alteração;
- o editor roda testes do pacote atual;
- um terminal separado executa `go test ./...` quando necessário;
- o CI roda suíte completa, race detector e análise estática.

Não esconda todos os checks dentro de uma string difícil de ler. Se a preparação tiver várias etapas, crie um script ou alvo de Makefile:

```makefile
.PHONY: dev dev-build test

dev:
	go tool air

dev-build:
	go test ./internal/... && go build -o ./tmp/api ./cmd/api

test:
	go test -race ./...
```

E use:

```toml
[build]
cmd = "make dev-build"
entrypoint = ["./tmp/api"]
```

Para aprofundar o fluxo, veja [Go Test: testing, race e cobertura](/aprenda/testes-go/) e o guia de [Staticcheck e golangci-lint](/blog/staticcheck-golangci-lint-go-qualidade-ci/).

## Air com Templ, sqlc e go generate

Projetos modernos podem gerar arquivos Go antes do build. Se você usa Templ, sqlc, mocks ou outro gerador, não quer recompilar antes que o arquivo gerado esteja atualizado.

Air suporta regras que executam um comando para tipos específicos de arquivo. Um exemplo com Templ:

```toml
[build]
cmd = "go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api"]
exclude_dir = ["tmp", "vendor"]
include_ext = ["go"]

[[build.rules]]
name = "templ"
include_ext = ["templ"]
cmd = "templ generate"
```

Quando um arquivo `.templ` muda, a regra executa `templ generate`. Os `.go` gerados entram no conjunto observado e disparam o build principal naturalmente.

Para sqlc, uma regra possível é:

```toml
[[build.rules]]
name = "sqlc"
include_dir = ["db/query", "db/migrations"]
include_ext = ["sql"]
cmd = "sqlc generate"
```

Evite loops: se o diretório de saída do gerador também dispara a mesma regra, cada geração pode provocar outra geração. Restrinja `include_dir`, `include_ext` e `exclude_regex` ao mínimo necessário.

Se seu frontend usa HTML gerado pelo Go, o tutorial de [Templ com HTMX](/blog/templ-go-componentes-html-htmx/) mostra como organizar componentes e handlers.

## Recarregar o navegador com o proxy do Air

Reiniciar a API não atualiza automaticamente uma aba do navegador. Air oferece um proxy opcional que encaminha requests para sua aplicação e injeta um pequeno script de reload em respostas HTML.

```toml
[proxy]
enabled = true
proxy_port = 8090
app_port = 8080
app_start_timeout = 10000
```

Nesse caso:

- a aplicação continua ouvindo em `http://localhost:8080`;
- você abre `http://localhost:8090` no navegador;
- depois de um build bem-sucedido, o proxy recarrega a página.

O HTML precisa conter `</body>` para a injeção funcionar. Arquivos CSS, JavaScript e templates também precisam estar dentro das regras de observação. Para uma API JSON sem páginas HTML, o proxy costuma ser desnecessário: reiniciar o servidor já resolve o ciclo principal.

Não exponha a porta do proxy em produção. Ela existe para conveniência local.

## Air com Docker Compose

Para desenvolver dentro de container, instale Air na imagem de desenvolvimento e monte o código-fonte como volume.

`Dockerfile.dev`:

```Dockerfile
FROM golang:1.27-alpine

WORKDIR /app

RUN go install github.com/air-verse/air@latest

COPY go.mod go.sum ./
RUN go mod download

CMD ["air", "-c", ".air.toml"]
```

`compose.yaml`:

```yaml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    working_dir: /app
    volumes:
      - .:/app
      - go-mod-cache:/go/pkg/mod
    ports:
      - "8080:8080"
    environment:
      APP_ENV: development
      DATABASE_URL: postgres://app:app@db:5432/app?sslmode=disable
    depends_on:
      - db

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  go-mod-cache:
  postgres-data:
```

Execute:

```bash
docker compose up --build
```

O bind mount `.:/app` é essencial: sem ele, o container enxerga apenas os arquivos copiados durante o build da imagem e não recebe as alterações do host.

Essa imagem é para desenvolvimento. A imagem de produção deve compilar em um estágio separado e copiar apenas o binário, sem Air, compilador ou código-fonte. Veja o guia de [Docker com Go e multi-stage build](/aprenda/golang-docker/) e as opções de [hospedagem para APIs Go](/blog/onde-hospedar-api-go-golang-deploy/).

### Rebuild não dispara no Docker Desktop ou WSL

Eventos do filesystem podem se comportar de forma diferente entre Linux nativo, Docker Desktop, WSL e volumes montados. Se Air funciona no host, mas não dentro do container:

1. confirme que o arquivo alterado aparece dentro do container;
2. verifique `working_dir` e destino do volume;
3. rode `air -d` para inspecionar os diretórios observados;
4. confira se a extensão está em `include_ext`;
5. ative polling na configuração gerada pelo Air, quando necessário;
6. evite observar caches e `node_modules`, que geram ruído excessivo.

Antes de culpar o watcher, execute manualmente dentro do container:

```bash
docker compose exec api go build ./cmd/api
docker compose exec api air -d
```

Se o build manual falha, o problema não é live reload.

## Variáveis de ambiente e segredos

Air pode carregar arquivos de ambiente configurados em `env_files`:

```toml
env_files = [".env.development", ".env"]
```

Arquivos posteriores sobrescrevem valores anteriores, mas variáveis que já existiam antes de Air iniciar não são substituídas. Isso permite definir defaults locais sem atropelar valores exportados pelo shell.

Não faça commit de senhas, tokens ou credenciais reais. Mantenha um `.env.example` com nomes e valores fictícios, documente as variáveis obrigatórias e use o mecanismo de secrets da plataforma fora do desenvolvimento. O guia de [configuração com Viper e variáveis de ambiente](/blog/configuracao-go-viper-variaveis-ambiente/) ajuda a separar configuração de código.

## Erros comuns e como corrigir

### `air: command not found`

O binário provavelmente foi instalado fora do `PATH`:

```bash
export PATH="$PATH:$(go env GOPATH)/bin"
which air
air -v
```

Se o projeto usa tool dependency, execute `go tool air` em vez de `air`.

### Air entra em loop de rebuild

Geralmente o binário ou outro arquivo gerado está dentro de um diretório observado. Coloque a saída em `tmp`, adicione esse diretório a `exclude_dir` e ao `.gitignore`.

Também procure geradores que reescrevem arquivos mesmo quando o conteúdo não mudou.

### `address already in use`

O processo anterior não encerrou ou outra aplicação está usando a porta. Confira:

```bash
lsof -i :8080
```

Use `send_interrupt = true` e um `kill_delay` compatível com o graceful shutdown. Seu programa também deve parar listeners e conexões ao receber `SIGINT`.

### A compilação funciona, mas Air inicia o binário errado

Garanta que `build.cmd` e `build.entrypoint` apontam para o mesmo artefato:

```toml
[build]
cmd = "go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api"]
```

Caminhos relativos são resolvidos a partir de `root`. Evite depender do diretório em que uma IDE por acaso abriu o terminal.

### Cada save demora demais

Reduza o trabalho do ciclo:

- não rode toda a suíte a cada rebuild;
- exclua `.git`, `vendor`, `tmp`, caches e assets irrelevantes;
- aumente `delay` se o editor salva vários arquivos em sequência;
- compile apenas o comando necessário;
- meça geradores como Templ, sqlc e mocks separadamente.

Go já oferece compilação rápida; uma configuração que observa milhares de arquivos ou executa cinco ferramentas serialmente pode eliminar essa vantagem.

## Configuração recomendada por cenário

### API pequena

Use build direto, poucas extensões e sem Docker:

```toml
[build]
cmd = "go build -o ./tmp/api ./cmd/api"
entrypoint = ["./tmp/api"]
include_ext = ["go"]
exclude_dir = ["tmp", "vendor"]
```

### API com templates server-side

Observe Go, templates e assets relevantes; ative proxy apenas se quiser reload do navegador.

### Projeto com geração de código

Use `[[build.rules]]` para cada gerador e mantenha o build principal simples. Não coloque `templ generate`, `sqlc generate`, mocks e testes completos em uma única linha sem medir o custo.

### Monorepo

Defina `root`, `include_dir` e `exclude_dir` com cuidado. Se existem vários binários independentes, pode ser mais claro manter uma configuração Air por serviço do que recompilar o repositório inteiro.

### Equipe com Docker

Use uma imagem de desenvolvimento separada, volume para código e cache de módulos. Fixe a versão da ferramenta quando o ambiente precisar ser reproduzível; não dependa eternamente de `@latest` em cada build.

## Air deve entrar no CI?

Normalmente, não como watcher. CI precisa executar comandos finitos e terminar:

```bash
go test ./...
go test -race ./...
go vet ./...
go build ./cmd/api
```

Pode fazer sentido instalar a mesma versão do Air apenas para validar que a ferramenta ainda inicia ou que o `.air.toml` continua parseável, mas manter um processo observando arquivos em CI não agrega valor.

A fronteira recomendada é:

- **desenvolvimento:** Air acelera o feedback;
- **CI:** comandos explícitos validam o projeto;
- **produção:** binário imutável e processo supervisionado pela plataforma.

Para releases de ferramentas e serviços, veja [GoReleaser, checksums e SBOM](/blog/goreleaser-go-binarios-checksums-sbom/).

## Checklist para adotar Air sem criar dívida

- [ ] O projeto executa corretamente sem Air.
- [ ] `build.cmd` compila apenas o binário necessário.
- [ ] `build.entrypoint` aponta para o artefato gerado.
- [ ] A pasta temporária está em `exclude_dir` e `.gitignore`.
- [ ] `vendor`, caches e diretórios grandes não são observados sem necessidade.
- [ ] O processo responde a interrupção e encerra recursos.
- [ ] Testes lentos não bloqueiam todo save sem justificativa.
- [ ] Geradores usam regras específicas e não entram em loop.
- [ ] Docker monta o código no mesmo `working_dir` usado pelo container.
- [ ] Segredos locais não são versionados.
- [ ] A versão da ferramenta é reproduzível para a equipe.
- [ ] Air não faz parte da imagem ou do comando de produção.

## Perguntas frequentes

### O que é Air em Go?

Air é uma ferramenta de live reload. Ela observa alterações, executa o build configurado e reinicia o programa quando a compilação termina com sucesso. É especialmente útil para APIs, aplicações web server-side e serviços locais.

### Como instalar Air?

Com Go 1.25+, use `go install github.com/air-verse/air@latest` para uma instalação global. Para associar a ferramenta ao módulo, use `go get -tool github.com/air-verse/air@latest` e execute `go tool air`.

### Air funciona com Docker Compose?

Sim. Instale Air na imagem de desenvolvimento, monte o repositório como volume e mantenha `working_dir` alinhado ao destino do mount. Se alterações não forem detectadas, verifique eventos do filesystem e polling.

### Hot reload e live reload são iguais?

No contexto do Air, o programa é recompilado e reiniciado. Não existe substituição de código dentro do processo antigo. Para HTML, o proxy opcional também consegue recarregar a página no navegador.

### Posso usar Air em produção?

Não é recomendado. Produção deve executar um binário compilado, versionado e imutável sob um supervisor ou orquestrador. Observar código-fonte e recompilar no servidor aumenta a superfície de falha.

## Conclusão

Air reduz o atrito diário do desenvolvimento Go sem invadir o código da aplicação. A configuração boa é pequena: um comando de build explícito, um entrypoint correto, uma pasta temporária excluída e apenas os arquivos necessários sob observação.

Comece com `air init`, simplifique o `.air.toml` para a estrutura real do projeto e confirme que cada etapa também funciona manualmente. Depois, se houver necessidade, adicione regras para Templ ou sqlc, proxy para o navegador e Docker Compose. Evite transformar o watcher em uma pipeline gigantesca ou levar a ferramenta para produção.

Para continuar, revise [API REST com Go](/aprenda/api-rest-go/), [graceful shutdown](/blog/graceful-shutdown-go-producao/), [testes em Go](/aprenda/testes-go/) e [Docker com Go](/aprenda/golang-docker/). A combinação de reload rápido, testes focados e um build de produção simples cria um fluxo local confortável sem comprometer a previsibilidade do deploy.

## Fontes

- [Air — repositório e documentação oficial](https://github.com/air-verse/air)
- [Documentação do comando go install](https://go.dev/ref/mod#go-install)
- [Go Modules Reference — tool dependencies](https://go.dev/ref/mod#go-mod-file-tool)
