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 denganimport nama.- Argumen (
{args[0]},{args[1]}) membuat snippet fleksibel seperti fungsi dengan parameter.import file.caddyfileatauimport /path/ke/direktori/*.caddyfileuntuk 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 validateuntuk memverifikasi seluruh konfigurasi multi-file sebelum deploy.