Map #
Direktif map memungkinkan kamu memetakan nilai dari satu variabel atau placeholder ke nilai lain menggunakan lookup table yang kamu definisikan. Hasilnya disimpan dalam satu atau lebih output variable yang bisa digunakan di bagian konfigurasi berikutnya.
Ini sangat berguna untuk kondisi yang lebih kompleks dari sekedar if/else — misalnya menentukan backend berdasarkan header, memetakan kode negara ke bahasa default, atau mengubah nilai query parameter menjadi sesuatu yang berbeda.
Sintaks Dasar #
map INPUT OUTPUT {
nilai_input_1 nilai_output_1
nilai_input_2 nilai_output_2
default nilai_default
}
example.com {
# Petakan header X-API-Version ke backend yang sesuai
map {header.X-API-Version} {backend_addr} {
"v1" "api-v1:8080"
"v2" "api-v2:8080"
"v3" "api-v3:8080"
default "api-v2:8080" # Default ke v2 jika header tidak ada
}
reverse_proxy {backend_addr}
}
Map dengan Multiple Output Variables #
Satu map bisa menghasilkan beberapa output sekaligus:
example.com {
# Satu input, dua output
map {header.Accept-Language} {lang} {lang_dir} {
~^id "id" "ltr" # Indonesia → kiri ke kanan
~^ar "ar" "rtl" # Arab → kanan ke kiri
~^he "he" "rtl" # Ibrani → kanan ke kiri
~^zh "zh" "ltr"
default "en" "ltr"
}
header Content-Language {lang}
header X-Text-Direction {lang_dir}
reverse_proxy backend:3000
}
Map untuk Routing Berdasarkan Header #
example.com {
# Routing berdasarkan header User-Agent (mobile vs desktop)
map {header.User-Agent} {site_variant} {
~(?i)mobile|android|iphone "mobile"
~(?i)tablet|ipad "tablet"
default "desktop"
}
# Handle berdasarkan hasil map
handle {
root * /var/www/{site_variant}
file_server
}
}
Map untuk Backend Berdasarkan Subdomain/Path #
*.example.com {
# Petakan subdomain ke backend yang sesuai
map {host} {tenant_backend} {
"acme.example.com" "acme-app:3000"
"globex.example.com" "globex-app:3001"
"initech.example.com" "initech-app:3002"
default "default-app:3000"
}
reverse_proxy {tenant_backend} {
health_uri /health
health_interval 15s
}
}
Map dengan Regex #
Prefix ~ menandakan nilai adalah regex:
example.com {
# Petakan path prefix ke backend
map {path} {service_backend} {
~/^\/api\/v1\/ "service-v1:8080"
~/^\/api\/v2\/ "service-v2:8080"
~/^\/static\/ "cdn-origin:9000"
~/^\/ws\/ "websocket-server:9090"
default "main-app:3000"
}
reverse_proxy {service_backend}
}
Map untuk Feature Flags #
app.example.com {
# Feature flag berdasarkan cookie atau header
map {cookie.feature_flags} {new_checkout} {
"enabled" "1"
"beta" "1"
default "0"
}
reverse_proxy backend:3000 {
# Teruskan feature flag ke backend sebagai header
header_up X-Feature-New-Checkout {new_checkout}
}
}
Map untuk Rate Limit Tier #
api.example.com {
# Tentukan rate limit tier berdasarkan API key header
# (dalam implementasi nyata, kamu mungkin pakai plugin auth)
map {header.X-Plan} {rate_limit_events} {
"free" "60"
"starter" "300"
"pro" "1000"
"enterprise" "10000"
default "30"
}
# Gunakan {rate_limit_events} di plugin rate_limit
# rate_limit {
# zone api_tier {
# key {header.X-API-Key}
# events {rate_limit_events}
# window 1m
# }
# }
reverse_proxy backend:8080
}
Map untuk Response Code Kustom #
example.com {
# Petakan kondisi error ke response code khusus
map {err.status_code} {custom_error_page} {
"400" "/errors/bad-request.html"
"401" "/errors/unauthorized.html"
"403" "/errors/forbidden.html"
"404" "/errors/not-found.html"
"429" "/errors/rate-limited.html"
"500" "/errors/server-error.html"
"502" "/errors/bad-gateway.html"
"503" "/errors/maintenance.html"
default "/errors/generic.html"
}
handle_errors {
rewrite * {custom_error_page}
file_server { root /var/www/html }
}
file_server { root /var/www/html }
}
Map untuk Lokalisasi #
example.com {
# Deteksi bahasa dari Accept-Language header
# lalu petakan ke path direktori yang sesuai
map {header.Accept-Language} {content_root} {
~^id "/var/www/html/id"
~^en "/var/www/html/en"
~^de "/var/www/html/de"
~^fr "/var/www/html/fr"
~^ja "/var/www/html/ja"
default "/var/www/html/en"
}
root * {content_root}
try_files {path} /index.html
file_server
}
Map dengan Placeholder Caddy sebagai Input #
example.com {
# Petakan environment ke konfigurasi yang sesuai
map {env.APP_ENV} {log_level} {
"production" "warn"
"staging" "info"
"development" "debug"
default "info"
}
log {
output file /var/log/caddy/access.log
level {log_level}
format json
}
reverse_proxy backend:3000
}
Map untuk A/B Testing Traffic Split #
app.example.com {
# Gunakan sebagian IP hash sebagai input untuk A/B split
# (ini adalah aproximasi sederhana, bukan solusi A/B testing yang sempurna)
map {remote_host} {ab_variant} {
# Hash berdasarkan last octet IP (0-99 = A, 100-255 = B)
# Pendekatan yang lebih baik: gunakan cookie khusus
default "a"
}
# Variasi A: backend lama
@variant_a var {ab_variant} a
reverse_proxy @variant_a old-backend:3000
# Variasi B: backend baru
reverse_proxy new-backend:3001
}
Debugging Map #
# Aktifkan debug log untuk melihat evaluasi map
# Di global options: { debug }
# Log akan menunjukkan nilai yang dihasilkan map
# Atau tambahkan header debug sementara:
example.com {
map {header.Accept-Language} {lang} {
~^id "id"
~^en "en"
default "en"
}
# Tambahkan sementara untuk debugging
header X-Debug-Lang {lang}
reverse_proxy backend:3000
}
# Cek nilai map dari response header
curl -H "Accept-Language: id-ID,id;q=0.9" -I https://example.com/
# Header X-Debug-Lang: id
curl -H "Accept-Language: en-US,en;q=0.9" -I https://example.com/
# Header X-Debug-Lang: en
Map untuk Cache-Control Berbeda per Tipe Konten #
example.com {
root * /var/www/html
# Map ekstensi file ke cache policy
map {path} {cache_control} {
~\.(js|css)$ "public, max-age=31536000, immutable"
~\.(png|jpg|webp)$ "public, max-age=2592000"
~\.(html)$ "no-cache, must-revalidate"
~/api/ "private, no-store"
default "public, max-age=3600"
}
header Cache-Control {cache_control}
encode gzip zstd
file_server
}
Dengan pendekatan ini satu konfigurasi map menggantikan banyak blok @matcher dan header terpisah — lebih bersih dan mudah dipelihara.
Ringkasan #
map INPUT OUTPUT { ... }memetakan nilai input ke output berdasarkan lookup table — jauh lebih bersih dari serangkaian kondisi if/else untuk banyak kasus.- Prefix
~pada nilai input menandakan regex — memungkinkan pattern matching yang fleksibel seperti~^iduntuk mencocokkan bahasa Indonesia.- Map bisa menghasilkan multiple output variables dalam satu operasi — gunakan saat satu input perlu menghasilkan beberapa nilai sekaligus.
- Selalu sertakan
defaultvalue untuk menangani input yang tidak cocok dengan pola manapun.- Output variable dari map menggunakan syntax
{nama_variable}dan bisa digunakan di handler, header, reverse_proxy, dan directive lain di dalam block yang sama.- Untuk debugging, tambahkan
header X-Debug-Map {output_var}sementara untuk melihat nilai yang dihasilkan map di response header.
← Sebelumnya: Templates Berikutnya: Access Log →
Map untuk Logging Level per Environment #
{
# Petakan environment variable ke log level
# Berguna saat mengelola banyak deployment (dev, staging, prod)
# dari Caddyfile yang sama dengan variable yang berbeda
}
example.com {
map {env.DEPLOY_ENV} {access_log_path} {
"production" "/var/log/caddy/prod-access.log"
"staging" "/var/log/caddy/staging-access.log"
"development" "stdout"
default "/var/log/caddy/access.log"
}
log {
output file {access_log_path}
format json
}
reverse_proxy backend:3000
}
Dengan cara ini satu Caddyfile bisa di-deploy ke environment yang berbeda hanya dengan mengubah nilai environment variable DEPLOY_ENV, tanpa perlu memelihara beberapa versi Caddyfile terpisah.
Map untuk Backend Tier #
api.example.com {
# Tier berdasarkan subdomain (tenant isolation)
map {host} {db_pool_size} {
"enterprise.api.example.com" "100"
"pro.api.example.com" "50"
"starter.api.example.com" "20"
default "10"
}
reverse_proxy backend:8080 {
# Kirim pool size ke backend sebagai hint untuk connection pooling
header_up X-DB-Pool-Size {db_pool_size}
}
}