Map

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 ~^id untuk mencocokkan bahasa Indonesia.
  • Map bisa menghasilkan multiple output variables dalam satu operasi — gunakan saat satu input perlu menghasilkan beberapa nilai sekaligus.
  • Selalu sertakan default value 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}
    }
}
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact