DockFlare: Aturan Tunnel Cloudflare Otomatis dari Label Docker, dan Env Var yang Tidak Berefek
Ada satu langkah di homelab yang hampir selalu dikerjakan manual: setiap kali Anda menjalankan docker compose up untuk service baru, Anda membuka dashboard Cloudflare, mencari tunnel, mengedit JSON ingress, menambah record CNAME, membuat Access Policy, lalu reload tunnel. Lima menit per service. Lima service sebulan, dan Anda mulai menyisihkan waktu untuk pekerjaan yang mekanis.
DockFlare adalah jawaban repo orang lain untuk itu. Ini review terhadap proyek pihak ketiga milik ChrispyBacon (github.com/ChrispyBacon-dev), bukan produk kami. Yang saya lakukan adalah membaca source Python-nya lalu melaporkan apa yang benar-benar ada di dalam, termasuk bagian yang belum rapi.
Ringkasnya: satu aplikasi Flask yang mendengarkan event Docker, membaca label berawalan dockflare. pada tiap container, lalu membuat dan menghapus aturan ingress, record DNS, dan aplikasi Cloudflare Access secara otomatis. Versi di config.py pada saat artikel ini ditulis adalah v3.1.6.
Bentuk repo
Kondisi branch stable saat ditulis: 770 entri di tree, 66 file .py, bahasa utama Python, lisensi GPL-3.0, 2.477 stars dan 107 fork, homepage dockflare.app. Branch default bukan main melainkan stable, jadi perlu diketahui kalau Anda mau clone.
Urutan modul terbesar menunjukkan di mana kompleksitas sebenarnya:
170369 dockflare/app/web/api_v2_routes.py
123388 dockflare/app/web/routes.py
61248 dockflare/app/core/reconciler.py
52886 dockflare/app/core/state_manager.py
48934 dockflare/app/core/docker_handler.py
46759 dockflare/app/core/cloudflare_api.py
38550 dockflare-agent/app/main.py
Polanya jelas: core/ berisi logika, sedangkan web/ berisi Flask routes yang jauh lebih besar dari logikanya sendiri. Ini aplikasi dengan web UI penuh, bukan skrip. Ada juga sub-proyek mail-manager dan webmail di balik compose profile email, plus dockflare-agent untuk skenario multi-server.
Empat label inti, bukan YAML panjang
Kontrak paling penting ada di docker_handler.py. Fungsi is_valid_service() hanya menerima tujuh bentuk string, dan semuanya dicocokkan dengan re.fullmatch terhadap pola yang dikompilasi:
http_https_pattern = rf"^(?:https?)://{host_ip_pattern}(?::{port_pattern})?$"
tcp_pattern = rf"^(?:tcp)://{host_ip_pattern}:{port_pattern}$"
ssh_pattern = rf"^(?:ssh)://{host_ip_pattern}:{port_pattern}$"
rdp_pattern = rf"^(?:rdp)://{host_ip_pattern}:{port_pattern}$"
http_status_pattern = r"^http_status:([1-5][0-9]{2})$"
if service_str == "bastion":
return True
Jadi dockflare.service=http://n8n:5678 valid, sedangkan dockflare.service=ftp://nas:21 ditolak dengan warning yang menyebutkan seluruh pola yang didukung. Ini keputusan desain yang layak dihargai: string service inilah yang diteruskan ke cloudflared, jadi validasi ketat di satu titik memang yang benar.
Bentuk pemakaiannya dari docker-compose.yml resmi repo:
labels:
- dockflare.enable=true
- dockflare.hostname=n8n.example.tld
- dockflare.service=http://n8n:5678
#- dockflare.access.group=YOUR-ACCESS-GROUP-ID
Label gate-nya jelas: tanpa dockflare.enable=true, container diabaikan sepenuhnya. Ada label turunan seperti path, zone_name, no_tls_verify, http2_origin, match_sni_to_host, originsrvname, dan http_host_header, serta varian terindeks dockflare.0.hostname untuk kasus seperti OAuth callback yang butuh hostname terpisah dari UI utama.
Reconcile, bukan sekadar event
Ini bagian paling menarik secara teknis, dan justru bukan yang README tonjolkan. Event Docker saja tidak cukup: kalau DockFlare mati sementara container masih berjalan, tidak ada yang membuat aturannya. Karena itu ada _run_reconciliation_logic() yang memindai ulang semua container secara berkala lalu membandingkan keadaan nyata dengan state internal.
containers = docker_client.containers.list(sparse=False, all=config.SCAN_ALL_NETWORKS)
container_count = len(containers)
batch_size = 3 if not config.USE_EXTERNAL_CLOUDFLARED else 2
for i in range(0, container_count, batch_size):
if time.time() - reconciliation_start_time > 60:
logging.warning("[Reconcile] Timeout during container scanning phase.")
current_app.reconciliation_info["status"] = "Container scan timeout (partial data)"
container_scan_complete = False
break
Perhatikan detail yang jarang terlihat di produk sejenis: reconciler punya deadline, dan ketika waktunya habis ia tidak diam-diam memakai data parsial. Ia menandai container_scan_complete = False lalu menulis status eksplisit ke reconciliation_info. Container dipindai dalam batch kecil, tiga per giliran, dengan batas 60 detik, sementara seluruh siklus punya max_total_time = 480.
Tiga hal lain yang layak dicatat. Pertama, update API punya retry berjenjang yang didefinisikan di config.py:
MAX_CF_UPDATE_RETRIES = 3
CF_UPDATE_RETRY_DELAY = 2
CF_UPDATE_BACKOFF_FACTOR = 2
Kedua, cleanup tidak asal hapus. cleanup_expired_rules_once() mengecek apakah record DNS atau aplikasi Access masih dipakai rule lain sebelum menghapus, lalu memverifikasi ulang kandidat di bawah state_lock tepat sebelum commit. Ketiga, ada migrasi label lama: dockflare.access.policy=bypass otomatis dipetakan ke public-default-bypass, dan authenticate dipetakan ke authenticated-default. Kalau email akun Cloudflare tidak tersedia, ia menolak membuat policy dan memberi warning, bukan diam-diam membuka akses.
Kenapa container mati tidak langsung menghapus subdomain
schedule_container_stop() tidak menghapus apa pun. Ia hanya menandai:
rule["status"] = "pending_deletion"
grace_delta = timedelta(seconds=grace_period)
rule["delete_at"] = datetime.now(timezone.utc) + grace_delta
rule["lifecycle_generation"] = int(rule.get("lifecycle_generation") or 0) + 1
Default grace period di config.py adalah 600 detik, dan CLEANUP_INTERVAL_SECONDS 60. Artinya docker compose restart tidak merebut subdomain Anda selama sepuluh menit. Tradeoff-nya jelas: Anda memilih antara downtime dan subdomain yatim, dan tool ini memilih yang terakhir.
Yang belum rapi
Bagian ini tidak bisa dilewati kalau Anda serius mempertimbangkan.
Satu env var mati. config.py mendefinisikan RECONCILIATION_BATCH_SIZE dengan default 5, tetapi reconciler.py meng-hardcode batch_size = 3 if not config.USE_EXTERNAL_CLOUDFLARED else 2. Nilai environment itu tidak pernah dibaca di sana. Kalau Anda ikut dokumentasi dan mengubahnya untuk beban besar, tidak ada efek: batch size selalu 3, atau 2 dengan cloudflared eksternal.
Nilai default yang tidak konsisten. GRACE_PERIOD_SECONDS bernilai 600 di config.py, tapi fallback di dalam kode adalah 28800 atau delapan jam. Jalur fallback ini hanya aktif kalau key hilang dari config, tapi menunjukkan bagian config dan bagian logika dikerjakan terpisah.
Akses Docker yang tetap powerful. DockFlare tidak bicara dengan Docker lewat socket read-only. Compose-nya memakai tecnativa/docker-socket-proxy:v0.4.1 dengan CONTAINERS=1, EVENTS=1, POST=1, dan juga EXEC=1, semuanya diteruskan ke DOCKER_HOST=tcp://docker-socket-proxy:2375. Proxy memang membatasi, tapi EXEC=1 adalah izin yang lebih longgar dari yang dibutuhkan untuk membaca label saja. Tetap jauh lebih baik daripada mount socket mentah, tapi ini bukan sandbox.
Image prebuilt. Compose menarik alplat/dockflare:stable dari Docker Hub, bukan membangun dari source di repo ini. Kalau Anda memakainya di produksi, Anda mempercayai image yang dibangun orang lain tanpa bisa memeriksa build-nya sendiri.
Ketergantungan mutlak pada Cloudflare. Tidak ada jalur Caddy, Traefik, atau nginx dalam desain ini. Kalau nanti Anda pindah dari Cloudflare Tunnel, seluruh state yang sudah dibangun tidak ada artinya.
Bootstrapping tidak satu perintah. Ia butuh Cloudflare API token dengan izin tulis zone dan DNS, persistent volume, Redis, plus network eksternal cloudflare-net yang harus dibuat lebih dulu. Compose juga mewajibkan INTERNAL_BOOTSTRAP_SECRET dengan nilai yang wajib diisi, karena ditulis sebagai ${INTERNAL_BOOTSTRAP_SECRET:?set INTERNAL_BOOTSTRAP_SECRET}.
Cocok untuk siapa
DockFlare masuk akal kalau Anda punya beberapa service yang perlu Cloudflare Access dan Anda bosan menjaga JSON ingress secara manual. Agak berlebihan kalau Anda cuma punya dua atau tiga service, sebab dashboard Cloudflare cukup cepat dan risikonya lebih rendah.
Satu perhatian khusus: kalau panel admin DockFlare sendiri mau diakses lewat tunnel, pastikan Anda me-comment port 5000:5000 di compose atau pasang Access Policy di depan UI-nya. Baris itu diberi komentar “Optional” tapi secara default masih aktif.
Penutup
Yang membuat DockFlare menarik bukan jumlah fiturnya, tapi kejujuran state machine-nya. Ia tahu bahwa DNS, Access App, dan tunnel config adalah tiga sumber kebenaran terpisah, dan ia menolak menghapus sebelum mengecek apakah yang lain masih memakainya. Itu sesuatu yang perlu ditulis manual kalau Anda mengotomasi sendiri, dan sering dilewatkan kalau Anda cuma menaruh trigger lalu delete.
Untuk yang masih ingin melihat detail, docker_handler.py adalah tempat terbaik untuk mulai. Lalu _run_reconciliation_logic() di core/reconciler.py adalah tempat terbaik untuk memahami cara berpikirnya.
Sumber: github.com/ChrispyBacon-dev/DockFlare, branch stable, lisensi GPL-3.0. Review ini dibaca dari source dan tidak menjalankan DockFlare di homelab saya, jadi klaim performa dan stabilitas jangka panjang tidak saya uji di sini.