Cara Setting Traefik v3 Docker Compose
Panduan menjalankan 5 aplikasi dalam satu IP menggunakan Traefik v3. Pelajari cara routing Host dan solusi error acme.json agar startup Docker lancar.
Satu IP, lima aplikasi, satu port 443
VPS Anda hanya 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 dalam satu server. Reverse proxy adalah proses yang menguasai port :80 dan :443, membaca header Host pada setiap permintaan, dan meneruskannya ke kontainer yang tepat. Traefik melakukan hal tersebut, serta mendapatkan dan memperbarui sertifikat untuk setiap hostname tanpa Anda perlu menjalankan certbot secara manual.
Perbedaan antara Traefik dengan blok nginx server {} terletak pada sumber konfigurasinya. Pada nginx, Anda mengedit file lalu melakukan reload, dan siklus hidup sertifikat tetap menjadi tugas terpisah — alur kerja yang Anda ikuti saat menerbitkan sertifikat Let's Encrypt dengan certbot pada nginx, di mana timer pembaruan berada di luar server web. Docker provider pada Traefik memantau aliran event Docker dan membaca labels dari kontainer Anda: jalankan kontainer dengan label aturan Host() dan kontainer tersebut dapat diakses dalam satu detik; hentikan kontainer dan rute tersebut akan hilang. Ini juga merupakan jebakan. Konfigurasi yang berada di dalam labels tersebar di lima tempat sekaligus, dan kesalahan label tidak akan memunculkan error — kontainer hanya tidak akan diarahkan, dan Traefik tidak akan memberikan pesan apa pun.
Empat kata benda
- Entrypoints adalah socket yang mendengarkan koneksi. Anda akan mendefinisikan dua entrypoint:
webpada:80danwebsecurepada:443. - Routers mencocokkan permintaan (
Host(...)) dan menghubungkannya ke sebuah service. Sertifikat diminta per router melaluitls.certresolver. - Services adalah backend — sebuah container dan port yang didengarkan di dalam jaringan Docker.
- Middlewares berada di antara router dan service: basic auth, daftar izin IP, penulisan ulang header, dan pengalihan (redirect).
Konfigurasi statis (entrypoints, providers, ACME) diberikan melalui command line Traefik atau di dalam traefik.yml, dan perubahannya memerlukan restart Traefik. Konfigurasi dinamis (routers, services, middlewares) berasal dari label container dan dimuat ulang secara otomatis (hot-reloaded). Mencampuradukkan keduanya adalah penyebab umum masalah "flag saya tidak berfungsi".
File compose
Satu jaringan Docker bersama bernama proxy adalah tulang punggungnya. Traefik hanya dapat menjangkau kontainer jika keduanya berada di dalam 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, kemudian docker compose logs -f traefik. Setiap aplikasi tambahan adalah salinan dari blok gitea dengan nama router, Host(), dan port internal masing-masing. Instalasi Nextcloud yang berjalan di Docker dengan TLS dan cadangan dapat dipasang dengan cara yang sama — hapus port yang dipublikasikan, hubungkan ke proxy, dan biarkan label router menangani hostname serta sertifikat.
Ada lima detail penting di sini.
exposedByDefault=false membuat kontainer tidak terlihat oleh Traefik sampai kontainer tersebut memiliki traefik.enable=true. Jika Anda menghapusnya, setiap kontainer yang Anda jalankan — termasuk postgres sementara yang Anda gunakan untuk pengecekan — akan mendapatkan rute otomatis.
providers.docker.network=proxy memberi tahu Traefik jaringan mana yang harus digunakan jika kontainer terhubung ke beberapa jaringan. Jika diabaikan, Traefik mungkin memilih IP kontainer yang salah, yang muncul sebagai error 502 yang tampak seperti kesalahan aplikasi.
loadbalancer.server.port=3000 adalah port di dalam kontainer; Gitea mendengarkan pada port 3000 di sana. Perhatikan bahwa tidak ada kontainer aplikasi yang memublikasikan port — hanya Traefik yang melakukannya.
Pengalihan pada entrypoint web mengubah permintaan plaintext menjadi 308 ke HTTPS. Port 80 tetap terbuka: tantangan ACME HTTP membutuhkannya, begitu juga pengguna yang mengetik hostname tanpa protokol.
Double $$ pada hash basic-auth adalah cara Compose melakukan escaping, 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 pada port 443 dan Traefik menjawab tantangan di dalam TLS handshake. Alternatif lainnya adalah HTTP-01 pada port 80 — ganti baris tlschallenge pada daftar command: Traefik dengan dua baris ini:
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=webKeduanya dapat digunakan. Keduanya memerlukan DNS publik untuk hostname sudah mengarah ke VPS Anda — otoritas sertifikat akan menyelesaikan nama tersebut dan terhubung dari luar. Buat record A (dan AAAA) terlebih dahulu, konfirmasi dengan dig +short git.example.com, lalu jalankan Traefik.
Berikut adalah jebakan yang sering membuang waktu pengguna. Traefik menyimpan kunci akun ACME dan setiap sertifikat yang diterbitkan dalam satu file acme.json. Jika file tersebut dapat dibaca oleh grup atau publik, Traefik akan menampilkan pesan seperti ini dan berhenti:
error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600Solusi yang tepat adalah cara di atas: gunakan bind-mount pada direktori dan biarkan Traefik membuat file tersebut sendiri dengan mode yang benar. Jika Anda membuat acme.json menggunakan touch, umask Anda membuatnya menjadi 644. Perbaiki di host:
chmod 600 ./letsencrypt/acme.json
docker compose restart traefikCadangkan direktori tersebut bersama dengan volume aplikasi Anda. Kehilangan file ini masih bisa diatasi — sertifikat dapat diterbitkan ulang — tetapi menerbitkan ulang lima hostname sekaligus akan membuat Anda terkena limitasi (rate limits).
Gunakan staging CA selama proses iterasi. Hilangkan tanda komentar pada baris caserver, pastikan semua rute berfungsi, lalu beri tanda komentar kembali dan hapus acme.json agar sertifikat produksi diminta ulang secara baru. Let's Encrypt produksi mengizinkan lima sertifikat duplikat per minggu untuk set hostname yang identik, dan membatasi validasi gagal yang berulang untuk nama yang sama. Staging menerbitkan sertifikat yang tidak terpercaya — browser Anda akan memberikan peringatan, dan peringatan tersebut adalah tanda bahwa proses berhasil — dengan limitasi yang jauh lebih longgar.
Dashboard adalah antarmuka kontrol, bukan demo
Sebagian besar panduan cepat mengatur --api.insecure=true, yang menjalankan dashboard pada port 8080 tanpa autentikasi. Hal ini berbahaya pada server dengan IP publik karena akan membocorkan topologi routing, hostname, nama middleware, dan port backend kepada siapa pun yang melakukan pemindaian.
Label pada layanan traefik di atas adalah alternatifnya: dashboard diarahkan seperti aplikasi lainnya, menggunakan hostname asli, melalui TLS, dan di belakang basicauth. service=api@internal berfungsi menghubungkan router ke API bawaan Traefik. Perketat keamanan dengan menambahkan daftar izin IP (IP allow-list) yang diterapkan dari kiri ke kanan. Jika alamat kantor Anda bersifat dinamis, atur rentang ke subnet yang diberikan oleh VPN WireGuard yang Anda host sendiri pada VPS yang sama dan akses dashboard hanya melalui tunnel tersebut:
- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-authDocker socket adalah root
/var/run/docker.sock adalah API yang dapat membuat container yang melakukan mount / dari host. Akses ke API ini setara dengan hak akses root pada mesin tersebut, dan Traefik membutuhkannya untuk membaca label.
Tetap gunakan :ro pada mount, namun pahami konsekuensinya: ini membuat file socket menjadi read-only. Hal ini tidak menghentikan permintaan POST ke Docker API yang dikirim melalui socket tersebut. Mitigasi yang sebenarnya adalah dengan tidak memberikan socket langsung ke Traefik, melainkan menempatkan proxy penyaring 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 memiliki akses baca ke container dan network, tetapi kehilangan kemampuan untuk membuat objek baru.
Firewall, port, dan aturan yang sering salah dipahami
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 rantai (chains) ufw. Akibatnya, kontainer yang dijalankan dengan ports: ["3000:3000"] tetap dapat diakses dari internet meskipun ada aturan ufw deny. Pertahanan ini bersifat struktural, bukan konfigurasi firewall: hanya publikasikan port dari Traefik, dan berikan networks: [proxy] pada kontainer lainnya. Jika sesuatu benar-benar harus menjangkau host, hubungkan ke loopback — "127.0.0.1:3000:3000".
Troubleshooting: errors you will actually see
404 page not found, disajikan oleh Traefik. Tidak ada router yang cocok. Berdasarkan kemungkinan terbesar: container kekurangan traefik.enable=true (dengan exposedByDefault=false terpasang); aturan Host() tidak cocok dengan nama yang Anda ketik; nama router pada satu label berbeda dengan nama router pada label lain (routers.gitea.rule dan routers.gitea.entrypoints harus berupa kata yang sama); atau Anda menggunakan tanda kutip untuk hostname alih-alih backticks. Traefik v3 memerlukan backticks di dalam matcher.
502 Bad Gateway. Router cocok tetapi backend tidak dapat dijangkau. Hampir selalu karena container tidak berada di jaringan 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 di tempat lain. Log menyebutkan upaya tersebut: dial tcp 172.18.0.5:8080: connect: connection refused.
Browser memberikan peringatan, dan sertifikat diterbitkan untuk TRAEFIK DEFAULT CERT. Tidak ada sertifikat untuk hostname tersebut dan Traefik menyajikan placeholder self-signed miliknya. Baca baris ACME berikut:
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. Perbaiki record, tunggu hingga TTL selesai, lalu restart Traefik.
Invalid response from http://git.example.com/.well-known/acme-challenge/... pada HTTP challenge: port 80 tidak dapat menjangkau Traefik dari luar — biasanya disebabkan oleh firewall tingkat penyedia layanan di depan VPS, bukan ufw.
Sertifikat tidak pernah terbit, dan DNS Anda menggunakan Cloudflare dengan ikon awan oranye aktif. Cloudflare mengakhiri TLS di edge mereka dan TLS-ALPN-01 tidak dapat selesai melaluinya. Atur record ke 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 mengakhiri TLS dan meneruskan plaintext ke :80; redirect entrypoint mengirimkannya kembali ke HTTPS. Hapus salah satu dari dua redirect tersebut.
Menjaga agar tetap berjalan
Unit Docker harus diaktifkan saat boot (systemctl is-enabled docker), dan restart: unless-stopped akan memulihkan stack setelah reboot. Untuk kontrol eksplisit, unit systemd kecil yang menjalankan docker compose -f /srv/edge/compose.yml up -d dengan RemainAfterExit=yes memberikan Anda systemctl status edge dan kontrol urutan.
Gunakan tag Traefik yang spesifik (traefik:v3.5, jangan gunakan latest). Upgrade dari v2 ke v3 mengubah sintaks aturan dan nama provider. Proses latest yang tidak terencana akan memuat ulang konfigurasi yang tidak lagi dipahami oleh sistem. Lakukan upgrade secara sengaja: baca catatan migrasi, perbarui tag, docker compose up -d traefik, dan pantau log. Jika Anda masih menggunakan tag v2, panduan migrasi Traefik v2 ke v3 menjelaskan setiap perubahan nama, mode kompatibilitas, dan proses rollback yang menjaga sertifikat Anda.
Cadangkan ./letsencrypt dan volume data setiap aplikasi. Traefik tidak menyimpan state lain yang tidak dapat dibangun kembali dari file compose.
Apa yang rusak saat skala membesar
Batasan pertama bukanlah throughput, melainkan satu unit server: satu Traefik pada satu VPS adalah single point of failure untuk lima aplikasi, dan acme.json menggunakan penyimpanan file — dua instansi Traefik yang menulis ke file tersebut akan menyebabkan korupsi data. Melakukan scaling out berarti memindahkan penyimpanan sertifikat keluar dari file, atau mengakhiri TLS di tempat lain.
Batasan kedua adalah koneksi berdurasi lama. Server-sent events, unggahan besar, dan klien lambat akan terkena timeout respons pada entrypoint; --entryPoints.websecure.transport.respondingTimeouts.readTimeout beserta writeTimeout dan idleTimeout adalah parameter pengaturannya. WebSocket dapat diteruskan tanpa konfigurasi tambahan.
Batasan ketiga adalah disk. --accesslog=true menulis ke stdout, dan driver json-file Docker akan menyimpannya selamanya kecuali jika dibatasi. Atur logging.options.max-size pada layanan Traefik, atau tulis log akses ke dalam file dan lakukan rotasi.
Semua ini tidak memerlukan orchestrator. Anda memerlukan server yang Anda kendalikan, dengan IP asli dan port 80 serta 443 terbuka untuk publik — satu VPS kecil sudah cukup untuk memenuhi semua kebutuhan tersebut.
FAQ
Apakah saya masih membutuhkan certbot jika saya menjalankan Traefik?
Tidak. ACME resolver pada Traefik meminta dan memperbarui sertifikat untuk setiap hostname yang diarahkan, lalu menyimpannya di acme.json. Certbot tetap menjadi alat yang tepat jika nginx atau server lain melakukan terminasi TLS sendiri; menjalankan keduanya pada hostname yang sama hanya akan menghabiskan limit rate Let's Encrypt.
Mengapa container saya mengembalikan 404 melalui Traefik?
Error 404 yang diberikan oleh Traefik berarti tidak ada router yang cocok dengan permintaan tersebut. Pastikan container memiliki traefik.enable=true (wajib setelah exposedByDefault=false diatur), nilai Host() sesuai dengan nama yang Anda ketik, dan nama router identik di setiap label untuk aplikasi tersebut. Traefik v3 juga memerlukan backticks di dalam matcher, bukan tanda kutip.
Apa perbedaan antara 404 dan 502 di sini?
404 berarti routing tidak pernah terjadi; 502 berarti router telah cocok tetapi backend menolak koneksi. Penyebab umum 502 adalah container yang tidak terhubung ke jaringan proxy, dan loadbalancer.server.port yang mengarah ke port yang dipublikasikan, bukan port tempat aplikasi mendengarkan di dalam container. Log akses akan menunjukkan alamat tepat yang dipanggil oleh Traefik.
Apakah melakukan mounting Docker socket secara read-only sudah cukup?
Flag :ro membuat file socket menjadi read-only, bukan API di baliknya — permintaan POST tetap berjalan melalui socket tersebut, dan akses API Docker setara dengan hak akses root pada host. Pengaturan yang lebih aman adalah container docker-socket-proxy yang ditunjukkan di atas, yang hanya memberikan akses baca container dan jaringan kepada Traefik dan memblokir akses tulis sepenuhnya.
Bisakah Traefik menerbitkan sertifikat wildcard?
Hanya melalui tantangan DNS-01, dengan menggunakan token API untuk penyedia DNS Anda. TLS-ALPN-01 dan HTTP-01 masing-masing memvalidasi satu hostname saja dan tidak dapat menghasilkan wildcard. DNS-01 juga merupakan solusi ketika CDN seperti Cloudflare melakukan terminasi TLS di depan VPS Anda dan dua tantangan lainnya gagal diselesaikan.