Snippet & Import #

Ketika kita pertama kali menggunakan Caddy, satu berkas Caddyfile kecil yang berisi beberapa baris konfigurasi sudah cukup untuk melayani website kita. Namun, seiring dengan berkembangnya infrastruktur kita — seperti bertambahnya jumlah subdomain, penerapan kebijakan keamanan (security headers) yang ketat di semua domain, pemisahan log lalu lintas data, dan penanganan autentikasi — berkas Caddyfile kita secara bertahap akan memanjang, menjadi rumit, dan dipenuhi dengan baris-baris kode yang berulang-ulang (duplication).

Dalam dunia rekayasa perangkat lunak, duplikasi kode adalah musuh utama pemeliharaan sistem. Jika kita menyalin 20 baris konfigurasi header keamanan ke 10 blok situs yang berbeda, dan di kemudian hari kita harus memperbarui satu parameter header, kita terpaksa melakukan perubahan di 10 tempat berbeda secara manual. Risiko kelalaian sangatlah tinggi.

Caddy memecahkan tantangan ini dengan menyediakan dua fitur modularitas yang sangat kuat: Snippet dan Import. Bersama-sama, keduanya menerapkan prinsip DRY (Don’t Repeat Yourself) ke dalam Caddyfile, memungkinkan kita membuat konfigurasi yang bersih, terstruktur, mudah dikelola, dan berskala besar (enterprise-ready).


Alur Kerja Kompilasi Multi-File Caddy #

Ketika Caddy membaca berkas Caddyfile utama, ia akan memproses instruksi import secara rekursif dan menyusun semua snippet serta berkas eksternal menjadi satu struktur konfigurasi tunggal di dalam memori sebelum diadaptasi menjadi JSON native.

Untuk memvisualisasikan bagaimana Caddy mengompilasi konfigurasi modular ini, mari perhatikan diagram alur berikut:

flowchart TD
    Main["Caddyfile Utama (/etc/caddy/Caddyfile)"] --> LoadGlobal["1. Load Global Options Block"]
    Main --> ImportSnippets["2. Import Snippet Modular\n(import snippets/*.caddyfile)"]
    Main --> ImportSites["3. Import Konfigurasi Situs\n(import sites/*.caddyfile)"]
    
    subgraph Filesystem["Sistem Berkas Modular"]
        direction LR
        S1["snippets/security.caddyfile"]
        S2["snippets/logging.caddyfile"]
        S3["sites/app1.caddyfile"]
        S4["sites/app2.caddyfile"]
    end
    
    ImportSnippets -.-> S1
    ImportSnippets -.-> S2
    ImportSites -.-> S3
    ImportSites -.-> S4
    
    S1 --> Compile["Engine Compiler Caddy (caddy adapt)"]
    S2 --> Compile
    S3 --> Compile
    S4 --> Compile
    Compile --> JSON["JSON Config Utama (Active Memory)"]

Apa itu Snippet? #

Snippet adalah blok konfigurasi bernama yang didefinisikan satu kali dan dapat disisipkan (reused) ke dalam blok situs mana pun di dalam Caddyfile.

Sintaks penulisan snippet dideklarasikan di level teratas Caddyfile (di luar blok situs mana pun) dengan membungkus nama snippet menggunakan tanda kurung biasa (nama_snippet). Konten di dalam snippet dapat berisi directive atau logika valid apa pun yang biasanya diletakkan di dalam blok situs.

Mari kita perhatikan perbandingan antara skenario duplikasi (Anti-Pattern) dan penggunaan Snippet (Benar):

# ANTI-PATTERN: Menyalin sekumpulan header keamanan ke setiap domain
domain1.com {
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        -Server
    }
    file_server
}

domain2.com {
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        -Server
    }
    reverse_proxy localhost:3000
}

# ====================================================================
# BENAR: Menggunakan Snippet untuk Mengeliminasi Duplikasi (DRY)
# ====================================================================

# 1. Definisikan Snippet di bagian atas berkas
(security_headers) {
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        -Server
    }
}

# 2. Impor Snippet ke masing-masing domain menggunakan 'import'
domain1.com {
    import security_headers
    file_server
}

domain2.com {
    import security_headers
    reverse_proxy localhost:3000
}

Dengan menggunakan snippet, jika kita ingin mengubah nilai header keamanan atau menambahkan header baru, kita cukup mengeditnya sekali di dalam snippet (security_headers). Perubahan tersebut akan langsung diterapkan ke seluruh blok situs yang mengimpornya.


Mengirimkan Argumen Dinamis ke Snippet #

Sering kali, konfigurasi kita hampir identik, tetapi memiliki satu atau dua nilai parameter yang berbeda. Misalnya, kita ingin struktur konfigurasi log kita sama untuk semua situs, tetapi nama berkas lognya harus mengikuti nama domain masing-masing.

Caddyfile mendukung pengiriman argumen dinamis ke dalam snippet, mirip dengan pengiriman parameter ke dalam sebuah fungsi pada bahasa pemrograman. Kita dapat mengakses nilai argumen tersebut menggunakan sintaksis {args[0]}, {args[1]}, dan seterusnya berdasarkan urutan posisinya saat diimpor.

Berikut adalah contoh implementasinya:

# 1. Definisikan Snippet dengan Placeholder Argumen
(standard_logging) {
    log {
        # {args[0]} akan digantikan dengan argumen pertama yang dikirimkan
        output file /var/log/caddy/{args[0]}.log {
            roll_size 50mb
            roll_keep 5
        }
        format json
    }
}

(custom_proxy) {
    # {args[0]} = alamat backend, {args[1]} = timeout koneksi
    reverse_proxy {args[0]} {
        transport http {
            dial_timeout {args[1]}s
        }
    }
}

# 2. Gunakan Snippet dengan Mengirimkan Parameter Nilai
site1.com {
    import standard_logging "site1-access"
    import custom_proxy localhost:3000 5
}

site2.com {
    import standard_logging "site2-access"
    import custom_proxy localhost:4000 10
}

Penanganan Argumen dengan Spasi atau Tanda Kutip #

Jika kita mengirimkan argumen yang mengandung spasi atau karakter khusus ke dalam snippet, kita wajib membungkus argumen tersebut dengan tanda kutip ganda pada baris import agar parser tidak memecahnya sebagai parameter terpisah:

(custom_header) {
    header X-Custom-Message "{args[0]}"
}

example.com {
    # Kirim argumen berupa string panjang dengan spasi
    import custom_header "Halo dari Caddy Web Server Produksi"
    file_server
}

Import Konfigurasi Eksternal (Multi-File) #

Instruksi import tidak hanya terbatas untuk membaca snippet internal yang ditulis di dalam satu berkas yang sama. Kita juga dapat menggunakannya untuk menyertakan konten dari berkas Caddyfile eksternal.

Fitur ini mendukung penggunaan pola wildcard (glob patterns) seperti tanda bintang (*), yang memungkinkan kita memisahkan konfigurasi menjadi puluhan berkas terpisah dan mengimpornya secara otomatis dalam satu baris instruksi.

# Mengimpor berkas konfigurasi situs dari direktori terpisah
import /etc/caddy/sites-enabled/*.caddyfile

# Mengimpor snippet khusus keamanan dari berkas tertentu
import /etc/caddy/snippets/security.conf

Tabel Perbandingan Struktur Modular Caddyfile #

Berikut adalah tabel pembanding antara pendekatan tata letak berkas konfigurasi tunggal, snippet internal, dan modularitas multi-file:

Kriteria AnalitikBerkas Tunggal MonolitikSnippet Internal (Satu Berkas)Struktur Multi-File (Import)
Skala KompleksitasSangat Rendah (1-2 situs)Menengah (3-5 situs)Tinggi / Enterprise (>5 situs)
Kemudahan PengaturanSangat MudahMudahMemerlukan struktur folder terencana
Tingkat KeterbacaanBuruk jika baris kode bertambahBaik (DRY internal)Sangat Baik (Modular penuh)
Kolaborasi Tim (Git)Sering memicu konflik mergeKonflik merge berkurangSangat ramah kolaborasi (satu berkas per situs)
Kemudahan DebuggingMudah (satu lokasi berkas)MudahMemerlukan pelacakan file impor eksternal
Rekomendasi SkenarioUji coba cepat / SandboxServer kecil personalServer produksi perusahaan / Cloud

Praktik Terbaik Organisasi Multi-File Skala Besar #

Untuk deployment di lingkungan produksi skala besar dengan banyak domain, sangat disarankan untuk menerapkan struktur direktori modular. Hal ini menjaga berkas konfigurasi tetap rapi dan mempermudah tim DevOps dalam mengelola rute situs.

Rekomendasi Struktur Folder #

/etc/caddy/
  ├── Caddyfile                    ← Berkas Utama (Entry Point)
  ├── snippets/
  │   ├── security.caddyfile       ← Kumpulan snippet header keamanan
  │   ├── logging.caddyfile        ← Kumpulan snippet konfigurasi log
  │   └── cors.caddyfile           ← Kumpulan snippet CORS
  └── sites-enabled/
      ├── app1.com.caddyfile       ← Konfigurasi situs App 1
      ├── app2.com.caddyfile       ← Konfigurasi situs App 2
      └── blog.net.caddyfile       ← Konfigurasi situs Blog

Implementasi Berkas Caddyfile Utama #

Berkas utama bertindak sebagai pusat kendali (orchestrator) yang memuat setelan global dan mengimpor semua modul lainnya:

# 1. Global Options
{
    email [email protected]
}

# 2. Impor Semua Snippet Pendukung
import /etc/caddy/snippets/*.caddyfile

# 3. Impor Semua Blok Situs Aktif
import /etc/caddy/sites-enabled/*.caddyfile

Implementasi Berkas /etc/caddy/snippets/security.caddyfile #

Menyimpan semua template keamanan reusable:

(security_strict) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Frame-Options "DENY"
        X-Content-Type-Options "nosniff"
        -Server
    }
}

(allow_iframe_sameorigin) {
    header {
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        -Server
    }
}

Implementasi Berkas /etc/caddy/snippets/logging.caddyfile #

Menyimpan template log modular:

(standard_log) {
    log {
        output file /var/log/caddy/{args[0]}.log {
            roll_size 100mb
            roll_keep 7
        }
        format json
        level INFO
    }
}

Implementasi Berkas /etc/caddy/snippets/cors.caddyfile #

Menyimpan template CORS untuk komunikasi lintas domain:

(cors_origin) {
    header {
        Access-Control-Allow-Origin "{args[0]}"
        Access-Control-Allow-Methods "GET, POST, OPTIONS"
        Access-Control-Allow-Headers "Content-Type, Authorization"
    }
}

Implementasi Berkas /etc/caddy/sites-enabled/app1.com.caddyfile #

Konfigurasi situs mandiri yang bersih karena memanfaatkan snippet global:

app1.company.com {
    # Impor snippet keamanan ketat dari file security.caddyfile
    import security_strict
    
    # Impor snippet log kustom
    import standard_log "app1-access"
    
    root * /var/www/app1
    file_server
}

Implementasi Berkas /etc/caddy/sites-enabled/app2.com.caddyfile #

Contoh situs API yang memadukan keamanan, log, dan CORS:

api.company.com {
    import allow_iframe_sameorigin
    import standard_log "api-access"
    import cors_origin "https://app1.company.com"
    
    reverse_proxy localhost:8080
}

Import Kondisional Berdasarkan Environment #

Kita dapat menggabungkan instruksi import dengan variabel lingkungan untuk membuat Caddy memuat konfigurasi yang berbeda secara dinamis antara server pengembangan lokal (development) dan server produksi (production).

# Berkas Caddyfile Utama
{
    # Membaca email dinamis dari env
    email {env.ACME_EMAIL}
}

# Caddy akan mengimpor konfigurasi TLS kustom sesuai variabel environment
# Misalnya jika {env.ENV_TYPE} bernilai 'dev', Caddy akan mencari file 'tls-dev.caddyfile'
# Jika bernilai 'prod', Caddy akan memuat 'tls-prod.caddyfile'
import /etc/caddy/snippets/tls-{env.ENV_TYPE}.caddyfile

example.com {
    import tls_config
    reverse_proxy localhost:3000
}

Isi berkas /etc/caddy/snippets/tls-dev.caddyfile:

(tls_config) {
    # Gunakan CA internal untuk development lokal
    tls internal
}

Isi berkas /etc/caddy/snippets/tls-prod.caddyfile:

(tls_config) {
    # Gunakan ACME Let's Encrypt standar dengan Cloudflare DNS
    tls {
        dns cloudflare {env.CLOUDFLARE_TOKEN}
    }
}

Perilaku Reload Otomatis (Auto-Reload & Watch) #

Ketika kita menjalankan Caddy di latar depan dengan opsi pengawasan berkas (--watch), Caddy akan memantau perubahan berkas untuk memicu reload otomatis secara real-time tanpa menghentikan lalu lintas server (zero-downtime hot reload).

# Jalankan Caddy dengan fitur pengawasan berkas aktif
caddy run --config /etc/caddy/Caddyfile --watch

[!IMPORTANT] Fitur --watch di Caddy sangat cerdas. Caddy tidak hanya memantau berkas entry-point utama /etc/caddy/Caddyfile, tetapi juga akan mendeteksi dan memantau seluruh berkas eksternal yang dimuat melalui direktif import (termasuk yang menggunakan pola glob). Jika ada perubahan di salah satu file situs di dalam folder /etc/caddy/sites-enabled/, Caddy akan mendeteksi modifikasi tersebut, melakukan validasi internal, dan memicu reload konfigurasi secara instan.


Anti-Pattern yang Harus Dihindari #

Berikut adalah beberapa kesalahan fatal terkait penggunaan snippet dan import yang sering menyebabkan server gagal dijalankan:

1. Mendefinisikan Snippet di Dalam Blok Situs #

Snippet wajib dideklarasikan di level teratas Caddyfile (global scope). Menuliskan definisi snippet di dalam kurung kurawal blok situs akan memicu error pembacaan parser.

# ANTI-PATTERN: Definisi bersarang
example.com {
    (my_snippet) {   # ← ERROR! Sintaks ini tidak valid di dalam blok situs
        header X-Test "value"
    }
}

# BENAR: Deklarasikan di luar blok situs terlebih dahulu
(my_snippet) {
    header X-Test "value"
}

example.com {
    import my_snippet
}

2. Impor Rekursif Tanpa Batas (Recursive Import) #

Hindari situasi di mana Berkas A mengimpor Berkas B, dan Berkas B juga mengimpor Berkas A secara bersamaan. Hal ini akan menyebabkan infinite loop saat Caddy mencoba mengurai konfigurasi dan akhirnya mematikan proses startup dengan status out of memory atau crash stack overflow.

3. Konflik Nama Snippet dengan Directive Bawaan #

Jangan pernah memberikan nama snippet yang sama persis dengan nama directive bawaan Caddy (seperti root, encode, reverse_proxy, dll.). Hal ini akan membuat parser bingung dan menganggap import Anda sebagai pemanggilan directive dengan argumen yang salah.

# ANTI-PATTERN: Nama snippet konflik dengan directive 'encode'
(encode) {
    header X-Encoded "true"
}

# BENAR: Berikan nama unik yang deskriptif
(gzip_plus_header) {
    encode gzip
    header X-Encoded "true"
}

Validasi dan Troubleshooting Konfigurasi Multi-File #

Jika kita menggunakan arsitektur multi-file dengan banyak instruksi import, menelusuri kesalahan sintaksis secara manual bisa menjadi sangat melelahkan. Caddy mempermudah proses ini dengan menyediakan pesan kesalahan yang sangat detail:

# Validasi berkas utama Caddyfile
caddy validate --config /etc/caddy/Caddyfile

Jika terjadi kesalahan di salah satu berkas yang diimpor, Caddy tidak hanya memberi tahu adanya error, tetapi juga akan menyebutkan path absolut dari berkas luar tersebut beserta nomor baris spesifik yang memicu kegagalan parsing.

Kita juga dapat menggunakan perintah adaptasi untuk memeriksa berkas konfigurasi akhir setelah semua import berhasil disisipkan oleh engine Caddy:

caddy adapt --config /etc/caddy/Caddyfile --adapter caddyfile > /tmp/resolved_config.json

Ringkasan #

  • Snippet dideklarasikan dengan format nama di dalam tanda kurung (nama_snippet) { ... } di bagian atas Caddyfile (global scope).
  • Masukkan argumen posisional ({args[0]}, {args[1]}) untuk membuat snippet berperilaku dinamis layaknya fungsi pemrograman.
  • Instruksi import dapat memuat konfigurasi dari snippet internal maupun berkas Caddyfile eksternal.
  • Gunakan pola wildcard glob (import /etc/caddy/sites-enabled/*.caddyfile) untuk memuat puluhan konfigurasi situs secara otomatis.
  • Pisahkan folder sites-enabled/ dan snippets/ untuk menjaga kerapian struktur berkas pada server skala produksi.
  • Jalankan perintah caddy validate sebelum melakukan reload server untuk memastikan tidak ada kesalahan sintaksis di dalam berkas impor luar.

← Sebelumnya: Matcher   Berikutnya: Global Options →

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