Traefik Reverse Proxy: 5 Aplikasi dalam 1 Compose
Jalankan lima aplikasi di satu IP dengan Traefik v3 dan Docker Compose. Pelajari routing Host, TLS Let's Encrypt otomatis, serta jebakan acme.json yang menghambat startup.
Satu IP, lima aplikasi, satu port 443
VPS Anda memiliki satu alamat IPv4 publik dan satu port TCP 443. Anda ingin menjalankan Gitea, salinan staging aplikasi Anda, dashboard internal, halaman status, dan penerima webhook di dalamnya: lima hostname pada satu server. Reverse proxy adalah proses yang menggunakan :80 dan :443, membaca header Host pada setiap permintaan, lalu meneruskannya ke container yang sesuai. Traefik melakukan hal tersebut dan memperoleh serta memperbarui sertifikat untuk setiap hostname tanpa Anda perlu menjalankan certbot secara manual. Nginx dan Caddy juga dapat menangani terminasi TLS untuk kelima hostname tersebut, jadi jika pilihannya belum ditetapkan, pertimbangkan ketiga proxy berdasarkan pengelolaan sertifikat dan biaya konfigurasi per aplikasi sebelum menghubungkan semuanya ke salah satunya.
Hal yang membedakan Traefik dari blok nginx server {} adalah sumber konfigurasinya. Dengan nginx, Anda mengedit file lalu memuat ulang konfigurasi, sedangkan siklus hidup sertifikat tetap menjadi pekerjaan terpisah. Itulah alur kerja yang digunakan saat Anda menerbitkan sertifikat Let's Encrypt dengan certbot pada nginx, ketika timer pembaruan berjalan sepenuhnya di luar web server. Provider Docker milik Traefik memantau aliran event Docker dan membaca label dari container Anda: jalankan container yang memiliki label aturan Host(), lalu container tersebut dapat menerima traffic dalam waktu kurang dari satu detik; hentikan container tersebut, lalu rutenya dihapus. Namun, hal ini juga menjadi sumber masalah. Konfigurasi yang disimpan dalam label tersebar di lima tempat sekaligus, dan label yang salah tidak menghasilkan pesan error; container tersebut hanya tidak dirutekan, tanpa pesan apa pun dari Traefik.
Empat istilah
- Entrypoints adalah socket yang mendengarkan koneksi. Anda akan menentukan dua entrypoint:
webpada:80danwebsecurepada:443. - Routers mencocokkan request (
Host(...)) dan menghubungkannya ke suatu service. Sertifikat diminta untuk setiap router melaluitls.certresolver. - Services adalah backend, yaitu container dan port tempatnya mendengarkan koneksi di dalam jaringan Docker.
- Middlewares berada di antara router dan service: basic auth, daftar IP yang diizinkan, penulisan ulang header, dan redirect.
Keempat istilah tersebut adalah nama yang digunakan Traefik untuk konfigurasi yang biasanya harus Anda tulis secara manual: router adalah sebuah server_name, service adalah target proxy_pass, sedangkan middleware adalah direktif header dan autentikasi yang Anda tetapkan sendiri saat membangun server block reverse proxy nginx baris demi baris.
Konfigurasi statis (entrypoints, providers, ACME) diberikan melalui command line Traefik atau dalam traefik.yml. Perubahannya mengharuskan Traefik dimulai ulang. Konfigurasi dinamis (routers, services, middlewares) berasal dari label container dan dimuat ulang secara otomatis. Kekeliruan membedakan keduanya biasanya menyebabkan masalah "flag saya tidak berfungsi".
File compose
Satu jaringan Docker bersama bernama proxy menjadi fondasinya. Traefik hanya dapat menjangkau container jika keduanya terhubung ke jaringan tersebut.
name: edge
networks:
proxy:
name: proxy
services:
traefik:
image: traefik:v3.5
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.network=proxy
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.le.acme.email=you@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
# while you iterate, point at staging so a mistake costs nothing:
# - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --api.dashboard=true
- --log.level=INFO
- --accesslog=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=le
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.middlewares=dashboard-auth
- traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REPLACE$$THIS
gitea:
image: gitea/gitea:1 # major-only pin keeps this demo copy-pasteable; pin an exact release in production
restart: unless-stopped
volumes:
- ./gitea:/data
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.gitea.rule=Host(`git.example.com`)
- traefik.http.routers.gitea.entrypoints=websecure
- traefik.http.routers.gitea.tls.certresolver=le
- traefik.http.services.gitea.loadbalancer.server.port=3000docker compose up -d, lalu docker compose logs -f traefik. Setiap aplikasi tambahan merupakan salinan blok gitea dengan nama router, Host(), dan port internalnya sendiri. Instalasi Nextcloud yang berjalan di Docker dengan TLS dan pencadangan dapat ditambahkan dengan cara yang sama: hapus port yang dipublikasikan, hubungkan ke proxy, lalu biarkan label router menangani hostname dan sertifikat.
Ada lima detail penting di sini.
exposedByDefault=false membuat container tidak terlihat oleh Traefik sampai container tersebut memiliki traefik.enable=true. Jika bagian ini dihilangkan, setiap container yang Anda jalankan, termasuk postgres sementara yang digunakan untuk pemeriksaan, akan dibuatkan rute.
providers.docker.network=proxy memberi tahu Traefik jaringan mana yang harus digunakan ketika sebuah container terhubung ke beberapa jaringan. Jika dihilangkan, Traefik dapat memilih IP container yang salah. Akibatnya muncul error 502 yang terlihat seperti masalah pada aplikasi.
loadbalancer.server.port=3000 adalah port di dalam container. Gitea mendengarkan pada port 3000 di sana. Perhatikan bahwa tidak ada container aplikasi yang memublikasikan port. Hanya Traefik yang melakukannya.
Redirect pada entrypoint web mengubah permintaan plaintext menjadi respons 308 ke HTTPS. Port 80 tetap terbuka karena ACME HTTP challenge memerlukannya. Port tersebut juga diperlukan oleh pengguna yang mengetik hostname tanpa skema.
$$ ganda dalam hash basic auth adalah mekanisme escaping Compose, bukan kesalahan pengetikan. Buat hash tersebut dengan htpasswd -nbB admin 'your-password' (paket apache2-utils), lalu gandakan setiap $.
Sertifikat dan jebakan acme.json
tlschallenge=true memilih TLS-ALPN-01: Let's Encrypt terhubung ke server Anda melalui 443, lalu Traefik menjawab challenge di dalam TLS handshake. Alternatifnya adalah HTTP-01 pada port 80. Ganti baris tlschallenge dalam daftar command: Traefik dengan dua baris berikut:
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=webKeduanya dapat digunakan. Keduanya mengharuskan DNS publik untuk hostname tersebut sudah mengarah ke VPS Anda, sehingga certificate authority dapat me-resolve nama tersebut dan terhubung dari luar. Buat record A (dan AAAA) terlebih dahulu, konfirmasikan dengan dig +short git.example.com, lalu jalankan Traefik.
Sekarang jebakan yang dapat menghabiskan waktu berjam-jam. Traefik menyimpan account key ACME dan setiap sertifikat yang diterbitkan dalam satu acme.json. Jika file tersebut dapat dibaca oleh group atau semua pengguna, Traefik mencetak baris yang sangat mirip dengan berikut ini lalu berhenti:
error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600Perbaikan yang tepat adalah cara di atas: lakukan bind-mount pada direktori dan biarkan Traefik membuat file tersebut sendiri dengan mode yang benar. Jika Anda membuat acme.json dengan touch, umask Anda menjadikannya 644. Perbaiki pada host:
chmod 600 ./letsencrypt/acme.json
docker compose restart traefikCadangkan direktori tersebut bersama volume aplikasi Anda. Jika hilang, masalahnya masih dapat dipulihkan karena sertifikat dapat diterbitkan ulang. Namun, menerbitkan ulang lima hostname sekaligus dapat membuat Anda terkena rate limit.
Gunakan CA staging selama proses iterasi. Hapus komentar pada baris caserver, pastikan setiap route berfungsi, lalu tambahkan kembali komentarnya dan hapus acme.json agar sertifikat production diminta dari awal. Let's Encrypt production mengizinkan lima sertifikat duplikat per minggu untuk kumpulan hostname yang identik, serta membatasi validasi gagal yang berulang untuk nama yang sama. Staging menerbitkan sertifikat yang tidak tepercaya sehingga browser menampilkan peringatan. Peringatan itu menandakan bahwa proses berhasil, dengan batas yang jauh lebih longgar.
Dashboard adalah permukaan kontrol, bukan demo
Sebagian besar quickstart menetapkan --api.insecure=true, yang menyajikan dashboard pada port 8080 tanpa autentikasi. Pada server dengan IP publik, konfigurasi ini memberikan topologi routing, hostname, nama middleware, dan port backend Anda kepada siapa pun yang melakukan pemindaian.
Label pada service traefik di atas adalah alternatifnya: dashboard dirutekan seperti aplikasi lain, menggunakan hostname yang sebenarnya, melalui TLS, dan berada di belakang basicauth. service=api@internal menghubungkan router ke API bawaan Traefik. Perketat konfigurasi ini dengan merangkai daftar izin IP yang diterapkan dari kiri ke kanan. Jika alamat kantor Anda bersifat dinamis, tetapkan rentang ke subnet yang diberikan oleh WireGuard VPN yang Anda host sendiri pada VPS yang sama dan akses dashboard hanya melalui tunnel:
- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-authSatu password bersama basicauth tidak lagi dapat dipertahankan ketika lima aplikasi memerlukan akun masing-masing. Slot middleware yang sama dapat menerima forwardauth, yang menyerahkan keputusan kepada Authentik, server single sign-on yang di-host sendiri, sehingga dashboard dan setiap route di sebelahnya berada di balik satu login yang dapat Anda cabut dari satu tempat.
Socket Docker setara dengan root
/var/run/docker.sock adalah API yang dapat membuat container yang memasang / dari host. Akses ke API ini setara dengan akses root pada mesin, dan Traefik memerlukannya untuk membaca label.
Pertahankan :ro pada mount, tetapi pahami manfaatnya: opsi ini membuat socket file hanya dapat dibaca. Opsi ini tidak mencegah request POST ke Docker API yang dikirim melalui socket tersebut. Mitigasi yang sebenarnya adalah tidak pernah memberikan socket kepada Traefik dan menempatkan proxy pemfilter di antaranya:
dockerproxy:
image: tecnativa/docker-socket-proxy # pin the current tag
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxyHapus volume socket dari Traefik dan arahkan provider ke proxy:
--providers.docker.endpoint=tcp://dockerproxy:2375Traefik tetap dapat membaca container dan network, tetapi tidak lagi dapat membuat objek apa pun.
Firewall, port, dan aturan yang sering disalahpahami
Dua port terbuka, ditambah SSH:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enablePort yang dipublikasikan Docker melewati ufw. Docker menyisipkan aturan iptables sendiri yang dievaluasi sebelum chain ufw. Karena itu, container yang dijalankan dengan ports: ["3000:3000"] tetap dapat diakses dari Internet meskipun terdapat aturan ufw deny. Pertahanannya bersifat struktural, bukan konfigurasi firewall: publikasikan port hanya dari Traefik, lalu berikan networks: [proxy] kepada setiap container lain dan jangan berikan port apa pun. Jika suatu layanan benar-benar harus mengakses host, bind layanan tersebut ke loopback, "127.0.0.1:3000:3000".
Pemecahan masalah: error yang benar-benar akan Anda lihat
404 page not found, disajikan oleh Traefik. Tidak ada router yang cocok. Berdasarkan kemungkinan, penyebabnya adalah: container tidak memiliki traefik.enable=true (dengan exposedByDefault=false yang ditetapkan); aturan Host() tidak cocok dengan nama yang Anda ketik; nama router pada satu label berbeda dari nama router pada label lain (routers.gitea.rule dan routers.gitea.entrypoints harus merupakan kata yang sama); atau Anda menulis hostname di dalam tanda kutip, bukan backtick. Traefik v3 mengharuskan backtick di dalam matcher.
502 Bad Gateway. Sebuah router cocok, tetapi backend tidak dapat dijangkau. Hampir selalu container tidak berada pada network proxy; periksa docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. Kemungkinan lainnya adalah loadbalancer.server.port yang salah: Anda memberikan port yang dipublikasikan, atau aplikasi mendengarkan pada port lain. Log menyebutkan upaya tersebut: dial tcp 172.18.0.5:8080: connect: connection refused.
Browser menampilkan peringatan, dan sertifikat diterbitkan untuk TRAEFIK DEFAULT CERT. Tidak ada sertifikat untuk hostname tersebut, sehingga Traefik menyajikan placeholder yang ditandatangani sendiri. Baca baris ACME:
unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.comDNS belum mengarah ke server tersebut. Perbaiki record, tunggu hingga TTL berakhir, lalu restart Traefik.
Invalid response from http://git.example.com/.well-known/acme-challenge/... pada HTTP challenge: port 80 tidak meneruskan koneksi ke Traefik dari luar. Biasanya penyebabnya adalah firewall pada tingkat provider yang berada di depan VPS, bukan ufw.
Sertifikat tidak pernah diterbitkan, dan DNS Anda menggunakan Cloudflare dengan orange cloud aktif. Cloudflare melakukan TLS termination di edge-nya, sehingga TLS-ALPN-01 tidak dapat diselesaikan melalui Cloudflare. Atur record menjadi DNS-only selama proses penerbitan, atau beralih ke DNS-01 challenge dengan API token. DNS-01 juga merupakan satu-satunya challenge yang dapat menerbitkan wildcard.
Redirect loop. Sesuatu di depan Traefik sudah melakukan TLS termination dan meneruskan plaintext ke :80; redirect pada entrypoint mengarahkannya kembali ke HTTPS. Hapus salah satu dari kedua redirect tersebut.
Menjaga layanan tetap berjalan
Unit Docker harus diaktifkan saat boot (systemctl is-enabled docker), dan restart: unless-stopped akan menjalankan kembali stack setelah reboot. Untuk kontrol yang lebih eksplisit, unit systemd kecil yang menjalankan docker compose -f /srv/edge/compose.yml up -d dengan RemainAfterExit=yes memberi Anda systemctl status edge dan kontrol urutan.
Tetapkan tag Traefik secara spesifik (traefik:v3.5, jangan pernah latest). Pemutakhiran dari v2 ke v3 mengubah sintaks aturan dan nama provider, sedangkan latest tanpa pengawasan akan memuat ulang konfigurasi yang sudah tidak dipahaminya. Lakukan pemutakhiran secara terencana: baca catatan migrasi, naikkan tag, docker compose up -d traefik, lalu monitor log. Jika Anda masih menggunakan tag v2, panduan migrasi Traefik v2 ke v3 menjelaskan setiap perubahan nama, mode kompatibilitas, dan rollback yang mempertahankan sertifikat Anda.
Cadangkan ./letsencrypt dan volume data setiap aplikasi. Traefik tidak menyimpan state lain yang tidak dapat Anda bangun ulang dari file compose.
Apa yang rusak saat skala meningkat
Batas pertama bukan throughput, melainkan satu server: satu Traefik pada satu VPS menjadi satu titik kegagalan untuk lima aplikasi, dan acme.json merupakan penyimpanan berbasis file datar; dua instance Traefik yang menulis ke sana akan merusaknya. Scaling out berarti memindahkan penyimpanan sertifikat ke luar file, atau menghentikan TLS di lokasi lain.
Masalah kedua adalah koneksi yang berumur panjang. Server-sent events, upload berukuran besar, dan client yang lambat dapat mencapai batas waktu respons entrypoint; --entryPoints.websecure.transport.respondingTimeouts.readTimeout beserta pasangan konfigurasinya, writeTimeout dan idleTimeout, adalah parameter pengaturnya. WebSocket dapat diteruskan tanpa konfigurasi tambahan.
Masalah ketiga adalah disk. --accesslog=true menulis ke stdout, dan driver Docker json-file menyimpan semua data tersebut selamanya kecuali diberi batas. Atur logging.options.max-size pada service Traefik, atau tulis access log ke file lalu lakukan rotasi.
Semua ini tidak memerlukan orchestrator. Namun, Anda memerlukan server yang dapat dikendalikan, dengan IP publik yang benar dan port 80 serta 443 terbuka untuk publik. Satu VPS kecil sudah mencakup seluruh daftar dependensi.
FAQ
Apakah saya masih memerlukan certbot jika menjalankan Traefik?
Tidak. Resolver ACME Traefik meminta dan memperbarui sertifikat untuk setiap hostname yang dirutekannya, lalu menyimpan semuanya di acme.json. Certbot tetap merupakan pilihan yang tepat ketika nginx atau server lain menangani TLS termination secara langsung. Menjalankan keduanya untuk hostname yang sama hanya menghabiskan batas laju Let's Encrypt.
Mengapa container saya mengembalikan 404 melalui Traefik?
404 yang disajikan oleh Traefik berarti tidak ada router yang cocok dengan request tersebut. Pastikan container memiliki traefik.enable=true (wajib setelah exposedByDefault=false ditetapkan), nilai Host() sesuai dengan nama yang Anda ketik, dan nama router identik pada semua label untuk aplikasi tersebut. Traefik v3 juga mengharuskan backtick di dalam matcher, bukan tanda kutip.
Apa perbedaan antara 404 dan 502 dalam kasus ini?
404 berarti routing tidak pernah terjadi. 502 berarti sebuah router cocok, tetapi backend menolak koneksi. Penyebab 502 yang umum adalah container tidak terhubung ke network proxy, atau loadbalancer.server.port mengarah ke port yang dipublikasikan, bukan port tempat aplikasi listen di dalam container. Access log mencantumkan alamat persis yang dihubungi Traefik.
Apakah mounting Docker socket dalam mode read-only sudah cukup?
Flag :ro membuat file socket menjadi read-only, bukan API di baliknya. Request POST tetap dikirim melalui socket tersebut, dan akses ke Docker API setara dengan akses root pada host. Pengaturan yang lebih kuat adalah container docker-socket-proxy yang ditampilkan di atas. Container tersebut hanya mengekspos operasi read untuk container dan network kepada Traefik serta memblokir operasi write sepenuhnya.
Dapatkah Traefik menerbitkan sertifikat wildcard?
Hanya melalui challenge DNS-01, dengan API token untuk penyedia DNS Anda. TLS-ALPN-01 dan HTTP-01 masing-masing memvalidasi satu hostname dan tidak dapat menghasilkan wildcard. DNS-01 juga diperlukan ketika CDN seperti Cloudflare menangani TLS termination di depan VPS Anda dan kedua challenge lainnya tidak pernah selesai.