Snippet & Import

Snippet & Import #

Seiring konfigurasi Caddy berkembang — lebih banyak domain, lebih banyak site, lebih banyak aturan — Caddyfile bisa menjadi panjang dan penuh dengan konfigurasi yang berulang. Snippet dan import adalah mekanisme Caddy untuk mengatasi duplikasi ini: mendefinisikan satu kali, gunakan berkali-kali.

Snippet adalah blok konfigurasi bernama yang bisa di-reuse, sedangkan import adalah cara untuk menyertakan konten dari snippet atau file eksternal. Bersama-sama, keduanya memungkinkan kamu mengorganisasi konfigurasi Caddy yang besar menjadi file-file yang terstruktur dan mudah dikelola.

Apa itu Snippet? #

Snippet didefinisikan di level atas Caddyfile (di luar blok site) menggunakan sintaks (nama_snippet). Konten snippet bisa berisi directive apapun yang valid di dalam blok site:

# Definisi snippet — di luar blok site manapun
(security_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server
        -X-Powered-By
    }
}

# Penggunaan snippet dengan import
example.com {
    import security_headers
    root * /var/www/html
    file_server
}

api.example.com {
    import security_headers    # Snippet yang sama digunakan lagi
    reverse_proxy localhost:8080
}

admin.example.com {
    import security_headers    # Dan lagi di sini
    basicauth {
        admin $2a$14$...
    }
    reverse_proxy localhost:9000
}

Tanpa snippet, kita harus menyalin blok header {...} ke setiap site. Dengan snippet, ada satu definisi yang jadi sumber kebenaran — jika perlu mengubah security headers, cukup ubah di satu tempat.


Mendefinisikan dan Menggunakan Snippet #

Sintaks Dasar #

# Definisi snippet
(nama_snippet) {
    # Konten snippet — directive yang valid
    directive1
    directive2 {
        subdirective
    }
}

# Penggunaan snippet
site.com {
    import nama_snippet
    # Semua konten snippet di-inject di sini
}

Snippet Bisa Berisi Apa Saja #

# Snippet dengan log configuration
(standard_log) {
    log {
        output file /var/log/caddy/{args[0]}.log {
            roll_size 100mb
            roll_keep 5
            roll_keep_for 168h
        }
        format json
        level INFO
    }
}

# Snippet dengan TLS configuration
(tls_with_dns) {
    tls {
        dns cloudflare {env.CLOUDFLARE_TOKEN}
        resolvers 1.1.1.1
    }
}

# Snippet dengan rate limiting (butuh plugin)
(rate_limit_api) {
    rate_limit {
        zone dynamic {
            key {remote_host}
            events 100
            window 1m
        }
    }
}

# Snippet dengan error handling
(error_pages) {
    handle_errors {
        rewrite * /errors/{err.status_code}.html
        file_server {
            root /var/www/errors
        }
    }
}

Snippet dengan Argumen #

Snippet mendukung argumen yang membuatnya lebih fleksibel — seperti parameter di sebuah fungsi:

# Snippet dengan placeholder {args[0]}, {args[1]}, dst
(reverse_proxy_with_health) {
    reverse_proxy {args[0]} {
        health_uri /health
        health_interval 10s
        health_timeout 5s
        health_status 200
    }
}

# Snippet untuk proxy dengan auth header
(internal_proxy) {
    reverse_proxy {args[0]} {
        header_up X-Internal-Auth {args[1]}
    }
}

# Snippet untuk logging dengan nama file kustom
(named_log) {
    log {
        output file /var/log/caddy/{args[0]}.log
        format json
    }
}

# Penggunaan dengan argumen
api.example.com {
    import reverse_proxy_with_health localhost:8080
    import named_log "api-access"
}

service.example.com {
    import internal_proxy localhost:3000 "my-secret-token"
    import named_log "service-access"
}

Argumen di-pass secara positional dan diakses dengan {args[0]}, {args[1]}, dan seterusnya.


Import dari File Eksternal #

import bukan hanya untuk snippet — ia juga bisa digunakan untuk menyertakan file Caddyfile lain:

# Caddyfile utama
{
    email [email protected]
}

# Import file konfigurasi per-site
import /etc/caddy/sites/*.caddyfile

# Import file konfigurasi khusus
import /etc/caddy/snippets/common.caddyfile
import /etc/caddy/snippets/security.caddyfile

Import mendukung glob pattern, sehingga kamu bisa mengorganisasi konfigurasi ke banyak file dan mengimport semuanya sekaligus dengan satu baris.


Organisasi Multi-File untuk Project Besar #

Ini adalah pola yang direkomendasikan untuk deployment dengan banyak domain dan konfigurasi kompleks:

Struktur Direktori #

/etc/caddy/
  ├── Caddyfile                    ← File utama (entry point)
  ├── snippets/
  │   ├── security.caddyfile       ← Security headers & TLS config
  │   ├── logging.caddyfile        ← Log configuration
  │   ├── cors.caddyfile           ← CORS headers
  │   └── rate-limits.caddyfile    ← Rate limiting rules
  └── sites/
      ├── example.com.caddyfile    ← Konfigurasi per domain
      ├── api.example.com.caddyfile
      └── admin.example.com.caddyfile

Caddyfile — File Utama #

# Global options
{
    email [email protected]
    
    # Log global errors
    log {
        output file /var/log/caddy/errors.log
        level ERROR
    }
}

# Import semua snippet
import /etc/caddy/snippets/*.caddyfile

# Import semua site
import /etc/caddy/sites/*.caddyfile

snippets/security.caddyfile #

(security_headers) {
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
        -Server
        -X-Powered-By
    }
}

(tls_cloudflare) {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        resolvers 1.1.1.1 8.8.8.8
    }
}

(block_sensitive_files) {
    @sensitive {
        path /.env /.git/* /config.yaml /docker-compose.yml *.bak *.sql
    }
    respond @sensitive 404
}

snippets/logging.caddyfile #

(json_log) {
    log {
        output file /var/log/caddy/{args[0]}.log {
            roll_size 50mb
            roll_keep 7
            roll_keep_for 336h   # 2 minggu
        }
        format json
        level INFO
        
        # Exclude health check dari log (mengurangi noise)
        except /health /ready /ping
    }
}

(console_log) {
    log {
        output stderr
        format console
        level DEBUG
    }
}

snippets/cors.caddyfile #

(cors_public) {
    header {
        Access-Control-Allow-Origin "*"
        Access-Control-Allow-Methods "GET, POST, OPTIONS"
        Access-Control-Allow-Headers "Content-Type, Authorization"
        Access-Control-Max-Age "86400"
    }
    
    @preflight method OPTIONS
    respond @preflight "" 204
}

(cors_restricted) {
    header {
        Access-Control-Allow-Origin "{args[0]}"
        Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS"
        Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
        Access-Control-Allow-Credentials "true"
        Access-Control-Max-Age "3600"
    }
    
    @preflight method OPTIONS
    respond @preflight "" 204
}

sites/example.com.caddyfile #

example.com, www.example.com {
    # Redirect www ke non-www
    @www host www.example.com
    redir @www https://example.com{uri} permanent
    
    import security_headers
    import block_sensitive_files
    import json_log "example-com"
    
    root * /var/www/example.com
    encode gzip zstd
    file_server
}

sites/api.example.com.caddyfile #

api.example.com {
    import security_headers
    import cors_restricted "https://app.example.com"
    import json_log "api-example-com"
    
    encode gzip
    
    reverse_proxy localhost:8080 {
        health_uri /health
        health_interval 30s
        
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

Import Kondisional Berdasarkan Environment #

Kamu bisa membuat Caddyfile yang berbeda untuk development vs production dengan memanfaatkan file import:

# Struktur untuk multi-environment
/etc/caddy/
  ├── Caddyfile
  ├── Caddyfile.dev
  └── snippets/
      ├── tls-prod.caddyfile    ← TLS dengan Let's Encrypt
      └── tls-dev.caddyfile     ← TLS dengan internal CA
# Caddyfile.dev — untuk development
{
    local_certs
}

(tls_config) {
    tls internal
}

import /etc/caddy/sites/*.caddyfile
# Caddyfile — untuk production
{
    email [email protected]
}

(tls_config) {
    tls {
        dns cloudflare {env.CF_TOKEN}
    }
}

import /etc/caddy/snippets/security.caddyfile
import /etc/caddy/sites/*.caddyfile

Anti-Pattern yang Harus Dihindari #

# ANTI-PATTERN 1: Snippet didefinisikan di dalam blok site
# Snippet harus didefinisikan di level atas, bukan di dalam site block
example.com {
    (my_snippet) {   # ← TIDAK VALID
        header X-Custom "value"
    }
}

# ANTI-PATTERN 2: Recursive import
# Jangan import file yang mengimport file semula
# snippets/a.caddyfile mengimport snippets/b.caddyfile
# snippets/b.caddyfile mengimport snippets/a.caddyfile
# → Ini akan menyebabkan error

# ANTI-PATTERN 3: Snippet dengan nama yang konflik dengan directive
# Jangan gunakan nama snippet yang sama dengan directive Caddy
(encode) {     # ← Konflik dengan directive 'encode'!
    header X-Encoded "true"
}

# BENAR: Gunakan nama yang deskriptif dan tidak konflik
(encoding_plus_headers) {
    encode gzip
    header X-Encoded "true"
}

Validasi Konfigurasi Multi-File #

# Validasi seluruh konfigurasi termasuk semua file yang diimport
caddy validate --config /etc/caddy/Caddyfile

# Lihat konfigurasi final setelah semua import di-resolve
caddy adapt --config /etc/caddy/Caddyfile --adapter caddyfile | jq .

# Jika ada error di file yang diimport, error message akan menyertakan
# path file dan nomor baris yang bermasalah

Tips Mengelola Snippet #

Dokumentasikan Argumen #

# snippet: proxy_with_options
# args[0]: upstream address (contoh: localhost:3000)
# args[1]: health check path (contoh: /health)
# args[2]: timeout dalam detik (contoh: 30)
(proxy_with_options) {
    reverse_proxy {args[0]} {
        health_uri {args[1]}
        health_interval 10s
        transport http {
            response_header_timeout {args[2]}s
        }
    }
}

Buat Snippet yang Composable #

# Snippet kecil yang bisa dikombinasikan
(compress) {
    encode gzip zstd
}

(no_cache) {
    header Cache-Control "no-store, no-cache"
}

(json_content) {
    header Content-Type "application/json"
}

# Digunakan secara terpisah atau bersama
api.example.com {
    import compress
    import no_cache
    import json_content
    respond `{"status":"ok"}` 200
}

Ringkasan #

  • Snippet ((nama) { ... }) didefinisikan di level atas Caddyfile — di luar blok site manapun — dan digunakan dengan import nama.
  • Argumen ({args[0]}, {args[1]}) membuat snippet fleksibel seperti fungsi dengan parameter.
  • import file.caddyfile atau import /path/ke/direktori/*.caddyfile untuk memecah konfigurasi ke banyak file.
  • Pola multi-file yang direkomendasikan: satu Caddyfile utama yang import semua snippet dan site configs dari direktori terpisah.
  • Snippet adalah implementasi prinsip DRY (Don’t Repeat Yourself) untuk Caddyfile — satu definisi, banyak penggunaan.
  • Gunakan caddy validate untuk memverifikasi seluruh konfigurasi multi-file sebelum deploy.

← Sebelumnya: Matcher   Berikutnya: Global Options →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact