Como publicar uma API Go na VPS com Docker e HTTPS

Fala aí, beleza? Pra colocar sua API Go na VPS com Docker e HTTPS, o caminho mais limpo é compilar a API num Dockerfile multi-stage (build com CGO_ENABLED=0 GOOS=linux e imagem final distroless rodando como nonroot) e subir a API junto com o Caddy num Docker Compose
Tenha acesso root e SSD NVMe, com 10% OFF extra pelo link da Hora de Codar.
Ver a oferta de VPS Parceria Hora de CodarA API fica sem porta publicada, presa numa rede privada, e só o Caddy escuta nas portas 80 e 443 pra emitir o HTTPS automaticamente 🙂
O cenário aqui é uma API Go simples numa VPS Linux com domínio próprio
Todo o código deste post é exemplo didático, montado em cima da documentação oficial, sem benchmark e sem teste executado pelo autor
O que você precisa antes de publicar a API Go na VPS
Você precisa de cinco coisas: Go com suporte, uma API com go.mod, uma VPS com Docker e acesso root, um domínio apontando pro servidor e as portas 80 e 443 liberáveis
Se liga em cada uma:
- Go em versão com suporte: só as duas últimas versões principais recebem correções (lançadas como revisões menores, do tipo 1.6.1, 1.6.2), e hoje elas são a 1.27 e a 1.26: a 1.25 já saiu do suporte. O último patch é o go1.27.3, lançado em 2026-10-19, com correções de segurança no pacote
crypto/x509; na linha 1.26, o último patch é o go1.26.10, também de 2026-10-19 - API com
go.modego.sum, ouvindo na porta 8080, igual ao guia oficial de Go da Docker - VPS Linux com Docker, Docker Compose e acesso root
- Domínio com registros A/AAAA apontando pro IP da VPS
- Portas 80 e 443 que você consiga liberar no firewall
Pra esse projeto, a recomendação principal da Hora de Codar é a VPS da Hostinger (este artigo contém links de afiliado da Hostinger)
O motivo é bem prático: os planos de VPS trazem acesso root, armazenamento NVMe, processadores AMD EPYC, rede de 1 Gbps, backups semanais gratuitos, firewall gerenciado pelo painel da VPS e API pública
E tem um detalhe que poupa trabalho: existe um template de VPS com Ubuntu 24.04 que já vem com docker-ce e docker-compose instalados
Ou seja, nada de ficar instalando Docker na mão 😀
Nos planos com template Docker também vem o Docker Manager, uma interface web no painel da VPS pra implantar, iniciar, parar e monitorar containers, sem custo extra
Ele é opcional aqui: o tutorial usa o Compose, que é o que deixa a rede privada e o healthcheck descritos num arquivo só
Passo a passo: API Go com Docker, rede privada e HTTPS na VPS
A estrutura final do projeto fica assim: main.go, go.mod, go.sum, Dockerfile, compose.yaml e Caddyfile, tudo na mesma pasta
Bora ver na prática?
- Crie a API e o Dockerfile multi-stage
Primeiro o porquê: num multi-stage build você usa várias instruções FROM, e cada FROM inicia um estágio novo (documentação de multi-stage)
Você compila num estágio cheio de ferramentas e copia SÓ o binário pro estágio final, o resto fica de fora da imagem
É como cozinhar numa cozinha bagunçada e levar só o prato pra mesa haha
A própria documentação de multi-stage da Docker mostra esse ganho na prática: ao copiar só os artefatos necessários pro estágio final, a imagem fica bem mais leve, sem o compilador do Go e sem as ferramentas de build que só servem durante a compilação
Antes do Dockerfile, a API mínima
Ela tem uma rota /health e um modo -healthcheck, que vai ser importante no passo 3:
package main
import (
"flag"
"log"
"net/http"
"os"
"time"
)
func main() {
healthcheck := flag.Bool("healthcheck", false, "testa a API e sai com 0 ou 1")
flag.Parse()
// Modo usado pelo healthcheck do Compose
if *healthcheck {
client := http.Client{Timeout: 3 * time.Second}
resp, err := client.Get("http://127.0.0.1:8080/health")
if err != nil || resp.StatusCode != http.StatusOK {
os.Exit(1)
}
os.Exit(0)
}
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API Go no ar"))
})
log.Fatal(http.ListenAndServe(":8080", mux))
}
Agora o Dockerfile. A primeira parte, de compilação, segue a ordem do guia oficial de Go da Docker: copia go.mod e go.sum, roda go mod download, só depois copia os arquivos .go e compila com CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping. Já o estágio final usa uma imagem distroless, seguindo a técnica de multi-stage da documentação da Docker, pra deixar só o binário na imagem, sem o compilador:
# Estágio 1: compila o binário
FROM golang:1.26 AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping
# Estágio 2: imagem final só com o binário
FROM gcr.io/distroless/base-debian12
WORKDIR /
COPY --from=build /docker-gs-ping /docker-gs-ping
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/docker-gs-ping"]
A tag golang:1.26 é escolha deste exemplo, pra ficar numa versão com suporte, não a tag fixa do guia
O EXPOSE 8080 só documenta a porta, ele não publica nada pra fora
E o USER nonroot:nonroot faz o processo rodar sem ser root dentro do container
Resultado esperado: uma imagem final com o binário /docker-gs-ping em cima da distroless, sem o compilador do Go dentro
Mais controle no próximo deploy.
Configure sua aplicação em uma VPS Hostinger com acesso root. Escolha os recursos conforme o que seu projeto precisa.
- SSD NVMe
- AMD EPYC
- Backups semanais
O erro comum deste passo: copiar o código antes do go mod download
O guia copia go.mod e go.sum, roda o download e só depois copia os *.go justamente pra não baixar as dependências de novo a cada mudança no código
Inverteu a ordem? Cada alteração num .go invalida o cache e lá vai download de novo…
- Monte o Compose com rede privada
Por padrão o Compose cria uma rede chamada <projeto>_default, coloca todos os serviços nela e cada um encontra o outro pelo nome do serviço via DNS interno
É por isso que o Caddy vai conseguir falar com api:8080 sem saber IP nenhum
Aqui a gente vai um passo além: cria uma rede com internal: true pra API
Que rede interna? É uma rede sem conectividade externa
O Caddy entra nessa rede E numa segunda rede não interna, então ele continua com acesso externo pela segunda
services:
api:
build: .
networks:
- interna
healthcheck:
test: ["CMD", "/docker-gs-ping", "-healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
caddy:
image: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- interna
- publica
depends_on:
api:
condition: service_healthy
networks:
interna:
internal: true
publica:
volumes:
caddy_data:
caddy_config:
Repara que o serviço api não tem ports
Quem chega de fora só enxerga o Caddy, e a API só é alcançável por dentro da rede interna 🙂
Essa mesma lógica de proxy na frente e app escondida aparece quando a gente publica uma API NestJS, muda a linguagem e o desenho é o mesmo
Resultado esperado: dois serviços, com a API isolada e o Caddy ligado às duas redes
O erro comum deste passo: deixar a API só na rede internal: true quando ela precisa chamar um serviço externo (um gateway de pagamento, uma API de terceiros e etc)
O internal: true corta a conectividade externa, então nesse caso a API também precisa estar ligada a uma rede não interna
- Configure o healthcheck da API
O healthcheck aceita os campos test, interval, timeout, retries e start_period
O comando de teste devolve um código de saída: 0 significa healthy e 1 significa unhealthy
Se você não definir, o interval padrão é 30s e o timeout padrão também é 30s
No Compose acima, os valores de timeout, retries e start_period são exemplos, ajuste pro seu caso
E o depends_on com condition: service_healthy faz o Caddy esperar o healthcheck da API passar antes de subir
Agora o pulo do gato: o comando do test precisa EXISTIR dentro da imagem
Não dá pra assumir que a imagem distroless traz shell ou curl, por isso o exemplo usa o próprio binário com a flag -healthcheck
Ele faz um GET em /health e sai com 0 ou 1, exatamente o contrato que o Docker espera
Resultado esperado: a API passa a aparecer como healthy no Compose depois que responde em /health
O erro comum deste passo: copiar um healthcheck com curl de outro projeto
Se o curl não estiver na imagem, o teste falha sempre, a API nunca fica healthy e o Caddy fica esperando pra sempre
- Escreva o Caddyfile com o domínio
O Caddy serve HTTPS automaticamente quando conhece o nome do domínio (HTTPS automático do Caddy)
Então o Caddyfile precisa ter o domínio e a diretiva reverse_proxy apontando pra API:
api.seudominio.com.br {
reverse_proxy api:8080
}
Troca api.seudominio.com.br pelo seu domínio real, beleza?
O reverse_proxy manda as requisições pro upstream, que aqui é o serviço api na porta 8080
No Compose, o Caddyfile é montado em /etc/caddy e os volumes /data e /config ficam persistidos, como indica a imagem oficial do Caddy no Docker Hub
Se quiser comparar com outra stack, o caminho com FastAPI, Docker e HTTPS segue a mesma ideia de proxy na frente
Resultado esperado: o Caddy recebe o tráfego no domínio e repassa pra api:8080 pela rede interna
O erro comum deste passo: não persistir o /data
É ali que ficam os certificados e as chaves TLS, e a imagem oficial avisa que persistir esse diretório é muito importante

Crie sistemas com IA para vender
Prompts, modelos e checklists para criar e publicar seu sistema com IA e preparar a oferta para o primeiro cliente.
Quero o e-book gratuito Receba o livro e os bônus no seu e-mail.Sem volume, recriou o container e lá se foram os certificados 😛
- Libere as portas 80 e 443 no firewall da VPS
O HTTPS automático depende das portas 80 e 443 abertas externamente, então elas precisam estar liberadas no firewall
Na Hostinger, o firewall da VPS é gerenciado pelo painel da VPS, na seção Security → Firewall, com regras de aceitar, rejeitar ou descartar por protocolo, porta e origem. Por padrão, o firewall descarta todo o tráfego, então é preciso criar regras liberando as portas que a stack usa (documentação do firewall da VPS)
Tome cuidado: não precisa abrir a 8080, a API não tem porta publicada e o tráfego chega nela só pelo Caddy
O erro comum deste passo: liberar só a 443 e esquecer a 80
O Caddy pede as duas abertas externamente pro HTTPS automático funcionar
- Suba a stack e valide
Com os registros A/AAAA do domínio já apontando pro IP da VPS, suba a stack com o Docker Compose na pasta do projeto
Agora confere três coisas:
- O serviço
apiaparece como healthy no estado dos containers do Compose - O domínio abre em
https://no navegador e mostra a resposta da API - Acessar o IP da VPS na porta 8080 não deve responder, porque a API não tem
portspublicados
Se as três baterem, tua API está no ar atrás do proxy, com HTTPS e isolada, muito massa 😀
O erro comum deste passo: a porta 8080 responder de fora
Isso indica que sobrou um ports no serviço api (de algum teste local, por exemplo), remove e sobe de novo
Erros comuns ao publicar API Go com Docker e HTTPS (e como resolver)
A maioria dos problemas nesse setup é de configuração, não de servidor
Antes de pensar em trocar qualquer coisa, passa por esta lista
O HTTPS não sai: o que verificar?
Causa: uma das condições do HTTPS automático do Caddy não foi atendida
Os registros A/AAAA não apontam pra VPS, as portas 80 e 443 estão fechadas, o domínio não está no Caddyfile ou o diretório de dados não é gravável e persistente
Solução: confere o DNS, as regras do firewall, o nome do domínio no Caddyfile e o volume de /data
Como prevenir: revisa as regras do firewall sempre que mexer no servidor e deixa o /data num volume desde o primeiro deploy
Por que o Caddy não sobe e fica esperando a API?
Causa: o depends_on com service_healthy segura o Caddy até a API ficar healthy
Se o teste retorna 1, ou se o comando do test nem existe na imagem, a API nunca fica healthy
Solução: confirma que o comando do healthcheck existe dentro da imagem final, como o binário com -healthcheck do exemplo, e que a rota /health responde
Como prevenir: nunca assuma shell ou curl em imagem distroless
A API não acessa serviços externos
Causa: ela está ligada só à rede com internal: true, que não tem conectividade externa
Solução: liga a API também a uma rede não interna, igual foi feito com o Caddy
Como prevenir: antes de isolar, lista quais serviços de fora a API chama
O build fica lento a cada alteração
Causa: o código foi copiado antes do go mod download, e qualquer mudança faz as dependências serem baixadas de novo
Solução: volta pra ordem do guia oficial: go.mod e go.sum, go mod download e só depois os *.go
Como prevenir: mantém essa ordem como padrão em todo Dockerfile Go
O certificado é emitido de novo depois de recriar o container
Causa: o /data do Caddy está sem volume, e os certificados e chaves TLS somem junto com o container
Solução: monta o /data (e o /config) em volumes nomeados, como no Compose do tutorial
Como prevenir: volumes persistentes desde o início e, como camada extra, os backups semanais da VPS
E vale lembrar: mantém o Go numa versão com suporte (hoje 1.27 ou 1.26), porque versão fora disso não recebe mais correção
Quer ver Docker rodando numa VPS em outro projeto?
Se tu curte ver a parte de servidor acontecendo, no canal tem um vídeo configurando Evolution API e n8n numa VPS, voltado pra automação com WhatsApp
Próximo passo depois da API no ar
Com a API rodando, o próximo passo é endurecer o servidor: firewall bem amarrado e fail2ban contra tentativa de acesso por força bruta
O outro hábito é manter a imagem atualizada: saiu patch novo do Go dentro das versões com suporte, atualiza a tag no estágio de build e reconstrói
Como o binário vai pra uma imagem distroless, só ele muda, o resto continua igual
Agora, se ainda não tem servidor, vale conferir a VPS para este projeto: o template com Ubuntu 24.04 já traz docker-ce e docker-compose, então tu sai direto pro passo 1 sem instalar Docker na mão
Até o próximo post! 😀
Perguntas frequentes
Quais versões do Go têm suporte hoje?
Hoje as versões com suporte são a 1.27 e a 1.26; a 1.25 já saiu do suporte. O Go mantém correções, inclusive de segurança, só nas duas versões principais mais recentes, lançadas como revisões menores (por exemplo, go1.27.3 e go1.26.10, ambas de 2026-10-19).
Por que a API não publica a porta 8080 no Compose?
Porque o acesso de fora deve passar só pelo Caddy, que escuta nas portas 80 e 443. Deixando a API sem ports e numa rede com internal: true, ela só é alcançada pelo proxy, o que reduz a superfície exposta ao público.
Preciso instalar curl na imagem distroless para o healthcheck funcionar?
Não é obrigatório, mas o comando do healthcheck precisa existir dentro da imagem. Como a imagem distroless não traz shell nem curl, o exemplo usa o próprio binário Go com uma flag -healthcheck que faz a checagem e devolve o código de saída esperado pelo Docker (0 para healthy, 1 para unhealthy).
O que acontece se eu não liberar a porta 80 no firewall da VPS?
O HTTPS automático do Caddy depende das portas 80 e 443 abertas externamente, além dos registros A/AAAA do domínio e de um diretório de dados persistente para os certificados. Sem a porta 80 liberada, a emissão do certificado tende a falhar.
Posso usar a Hostinger para hospedar essa stack?
Sim. Os planos de VPS da Hostinger incluem acesso root, armazenamento NVMe, processadores AMD EPYC, rede de 1 Gbps e backups semanais gratuitos, além de um template com Ubuntu 24.04 que já vem com docker-ce e docker-compose instalados, o que poupa o passo de instalar o Docker manualmente.
Quer aprender mais sobre inteligência artificial e programação na prática? Conheça o canal Hora de Codar no YouTube e acompanhe os próximos vídeos.
Formações
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 48min
Blog | Mais populares

Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]

Checklist de segurança n8n VPS 2026: proteja sua automação
Confira o checklist de segurança n8n VPS 2026 e mantenha sua automação protegida. Veja práticas essenciais e atualizadas para seu servidor. Manter seu ambiente de […]
Quanto custa uma VPS? Preço atualizado 2026
Quando se fala em quanto custa uma VPS, é preciso contextualizar o tema a partir de um provedor específico, porque o preço só faz sentido […]
