
Esse parece ser um tópico um tanto quanto... distópico? Talvez nem tanto. Mas todas as referências sobre o assunto na Internet estão incompletos e geralmente são pra versões mais antigas do traefik. Traefik é um roteador de conexões que funciona tanto como programa chamado por systemd (ou outro sistema de init se for um BSD), ou por container com algo como docker-compose, ou ainda diretamente em kubernetes. E não só funciona como proxy-reverse: pode também atuar como um servidor http pra suas conexões. E não somente tcp como udp. E escrito em Go!
Eu precisei colocar um servidor ssh atrás de um traefik. E deu um certo trabalho. Pra testar e mostrar aqui também, usei docker-compose pro serviço. Isso facilitou a configuração. Pra backend com ssh, subi uma instância do gitea com o ssh na porta 2222. Usei também certificados letsencrypt nas portas https do serviço.
Como um plus, ainda adicionei um allowlist pra acessar o serviço somente de certos IPs.
Vamos pra configuração então. Primeiramente do traefik.
Pra subir, eu configurei pra ouvir nas portas 80 (http), 443 (https), 8080 (traefik dashboard) e 2222 (ssh).
O conteúdo de traefik/docker-compose.yml
:
services:
traefik:
image: "traefik:v3.3"
container_name: "traefik"
restart: unless-stopped
environment:
- TZ=Europe/Stockholm
ports:
- "80:80"
- "443:443"
- "8080:8080"
- "2222:2222"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik.yml:/traefik.yml:ro"
networks:
- traefik
networks:
traefik:
name: traefik
Junto com essa configuração de container, ainda inclui a configuração do serviço em traefik/traefik.yml
:
api:
dashboard: true
insecure: true
debug: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
ssh:
address: ":2222"
serversTransport:
insecureSkipVerify: true
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik
certificatesResolvers:
letsencrypt:
acme:
email: This email address is being protected from spambots. You need JavaScript enabled to view it.
storage: /letsencrypt/acme.json
httpChallenge:
# used during the challenge
entryPoint: web
log:
level: WARN
Aqui é possível ver que defino novamente os entrypoints pra 80, 443 e 2222. E no 80 (http) eu redireciono pro 443 (https). E certificado do letsencrypt.
Tudo pronto no lado do traefik. Agora é o ponto de subir o serviço e preparar algumas coisas pra testar.
# docker compose -p traefik -f traefik/docker-compose.yml up -d
[+] Running 1/1
✔ Container traefik Started 0.4s
Dentro do diretório traefik
será criado um diretório letsencrypt
e dentro dele terá o arquivo acme.json
com seus certificados.
Primeiramente vamos subir um container de teste pra ver se tudo funciona.
O próprio projeto traefik fornece um container chamado whoami pra isso.
O conteúdo de whois/docker-compose.yml
:
services:
whoami:
container_name: simple-service
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.tests.loureiro.eng.br`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
- traefik
networks:
traefik:
name: traefik
E habilitando o serviço com docker-compose
:
# docker compose -p whoami -f whoami/docker-compose.yml up -d
WARN[0000] a network with name traefik exists but was not created for project "whoami".
Set `external: true` to use an existing network
[+] Running 1/1
✔ Container simple-service Started 0.3s
E um simples teste do serviço:
❯ curl -I http://whoami.tests.loureiro.eng.br
HTTP/1.1 308 Permanent Redirect
Location: https://whoami.tests.loureiro.eng.br/
Date: Thu, 24 Apr 2025 12:43:55 GMT
Content-Length: 18
❯ curl https://whoami.tests.loureiro.eng.br
Hostname: 0da540e1039c
IP: 127.0.0.1
IP: ::1
IP: 172.18.0.3
RemoteAddr: 172.18.0.2:52686
GET / HTTP/1.1
Host: whoami.tests.loureiro.eng.br
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 1.2.3.4
X-Forwarded-Host: whoami.tests.loureiro.eng.br
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: 4cacedb0129a
X-Real-Ip: 1.2.3.4
O primeiro curl
pro endpoint na porta 80 (http) recebe um redirect pra https.
Perfeito!
O segundo, os dados do container de forma transparente.
O serviço funciona! Mas pra http e https. Falta ssh na porta 2222, que está nessa porta pra não atrapalhar o uso do ssh normal do sistema.
Pra isso eu configurei um gitea, como descrevi no início do artigo.
Seu docker-compose.yml
é o seguinte:
services:
server:
image: gitea/gitea:1.23-rootless #latest-rootless
container_name: gitea
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=A4COU5a6JF5ZvWoSufi0L1aomSkzqww7s1wv039qy6o=
- LOCAL_ROOT_URL=https://gitea.tests.loureiro.eng.br
- GITEA__openid__ENABLE_OPENID_SIGNIN=false
- GITEA__openid__ENABLE_OPENID_SIGNUP=false
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__service__SHOW_REGISTRATION_BUTTON=false
- GITEA__server__SSH_DOMAIN=gitea.tests.loureiro.eng.br
- GITEA__server__START_SSH_SERVER=true
- GITEA__server__DISABLE_SSH=false
restart: always
volumes:
- gitea-data:/var/lib/gitea
- gitea-config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
#ports:
# - "127.0.0.1:3000:3000"
# - "127.0.0.1:2222:2222"
depends_on:
- db
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.giteaweb.rule=Host(`gitea.tests.loureiro.eng.br`)"
- "traefik.http.routers.giteaweb.entrypoints=websecure"
- "traefik.http.routers.giteaweb.tls=true"
- "traefik.http.routers.giteaweb.tls.certresolver=letsencrypt"
- "traefik.http.services.giteaweb.loadbalancer.server.port=3000"
- "traefik.http.middlewares.giteaweb-ipwhitelist.ipallowlist.sourcerange=1.2.3.4, 4.5.6.7"
- "traefik.http.routers.giteaweb.middlewares=giteaweb-ipwhitelist"
- "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)"
- "traefik.tcp.routers.gitea-ssh.entrypoints=ssh"
- "traefik.tcp.routers.gitea-ssh.service=gitea-ssh-svc"
- "traefik.tcp.services.gitea-ssh-svc.loadbalancer.server.port=2222"
- "traefik.tcp.middlewares.giteassh-ipwhitelist.ipallowlist.sourcerange=1.2.3.4, 4.5.6.7"
- "traefik.tcp.routers.gitea-ssh.middlewares=giteassh-ipwhitelist"
db:
image: postgres:17
restart: always
container_name: gitea_db
environment:
- POSTGRES_DB=gitea
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=A4COU5a6JF5ZvWoSufi0L1aomSkzqww7s1wv039qy6o=
networks:
- traefik
volumes:
- gitea-db:/var/lib/postgresql/data
networks:
traefik:
name: traefik
volumes:
gitea-data:
gitea-config:
gitea-db:
Pode ser visto que existe uma regra pra parte web, que roda na porta 3000 do container, e pra parte de ssh, que fica na porta 22222. Eu deixei comentado a parte que exporta as portas pra mostrar claramente que isso não é necessário. Aliás não funciona se especificar as portas.
E finalmente rodando o serviço:
# docker compose -p gitea -f gitea/docker-compose.yml up -d
WARN[0000] a network with name traefik exists but was not created for project "gitea".
Set `external: true` to use an existing network
[+] Running 2/2
✔ Container gitea_db Started 0.3s
✔ Container gitea Started 1.0s
E vamos ao testes.
❯ curl -s https://gitea.tests.loureiro.eng.br | head -n 10
<!DOCTYPE html>
<html lang="en-US" data-theme="gitea-auto">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gitea: Git with a cup of tea</title>
<link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwLyIsImljb25zIjpbeyJzcmMiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvYXNzZXRzL2ltZy9sb2dvLnBuZyIsInR5cGUiOiJpbWFnZS9wbmciLCJzaXplcyI6IjUxMng1MTIifSx7InNyYyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9hc3NldHMvaW1nL2xvZ28uc3ZnIiwidHlwZSI6ImltYWdlL3N2Zyt4bWwiLCJzaXplcyI6IjUxMng1MTIifV19">
<meta name="author" content="Gitea - Git with a cup of tea">
<meta name="description" content="Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go">
<meta name="keywords" content="go,git,self-hosted,gitea">
<meta name="referrer" content="no-referrer">
❯ telnet gitea.tests.loureiro.eng.br 2222
Trying 1.2.3.4...
Connected to gitea.tests.loureiro.eng.br.
Escape character is '^]'.
SSH-2.0-Go
^]
telnet> q
Connection closed.
E pronto! Temos o serviço funcionando e roteado pelo traefik. E podendo fazer ACL de ip do serviço sem precisar do firewall pra isso.
Boa diversão!
Nota: eu coloquei um básico da configuração do gitea pra exemplo. E não funciona se copiar e colar. Pra ter um serviço rodando, terá de adicionar mais algumas partes em environment.