//BAGIAN 1 DARI 10
Bagian 1: Kenapa Saya Bikin Secure CI/CD Pipeline, dan Persiapan Tools-nya
· 7 menit baca
English//TUTORIAL · 1 DARI 10 — Secure CI/CD Pipeline dengan Go, GitHub Actions, Vault, dan Dokploy
- 01Bagian 1: Kenapa Saya Bikin Secure CI/CD Pipeline, dan Persiapan Tools-nya
- 02Bagian 2: Vault, Secret, Policy, dan AppRole per Environment
- 03Bagian 3: Aplikasi Go Kecil yang Membaca Secret dari File dan Menolak Jalan Kalau Config Salah
- 04Bagian 4: Vault Agent Sidecar dan Docker Compose, Secret Masuk Tanpa App Tahu Vault Ada
- 05Bagian 5: Repo GitHub dan Workflow PR sebagai Security Gate
- 06Bagian 6: Ruleset yang Memblokir Merge, dan Lima Demo PR yang Harus Gagal
- 07Bagian 7: Menyiapkan Environment Dev dan Production di Dokploy
- 08Bagian 8: Workflow Deploy, Build Sekali, Scan, SBOM, GHCR, lalu Promosi ke Production
- 09Bagian 9: Proses Exception untuk Temuan Security yang Belum Bisa Diperbaiki
- 10Bagian 10: CI Mengambil Token Dokploy dari Vault lewat GitHub OIDC, dan Penutup Seri
Di banyak tim yang pernah saya lihat, jalur dari commit sampai production itu bergantung pada ingatan orang. Ingat jalankan scanner. Ingat jangan commit .env. Ingat cek dependency. Begitu ada yang lupa sekali saja, ya sudah, lolos.
Di seri ini saya mau membalik itu. Security nggak dijalankan karena orang ingat, tapi karena platformnya memaksa. Kalau ada yang nggak beres, PR-nya nggak bisa di-merge. Titik.
Proyeknya saya beri nama secure-cicd-go. Repo-nya publik di github.com/dimss113/secure-cicd-go, jadi kamu bisa lihat hasil akhirnya sambil mengikuti seri ini.
Catatan
Aplikasi Go-nya sengaja dibuat sangat sederhana. Yang jadi "produk" di sini adalah pipeline-nya, bukan aplikasinya.
Apa yang mau dibuktikan
Saya mau menunjukkan bahwa security bisa dipasang otomatis di alur kerja developer, dari pull request sampai deploy, hanya pakai tools gratis atau open source:
Developer buka PR, lalu kodenya otomatis dicek: test, secret, SAST, dependency, Dockerfile, dan image.
Ada yang nggak beres, PR nggak bisa di-merge.
Kalau lolos, image dibangun sekali, di-scan, dibuatkan SBOM, disimpan di registry, lalu image yang sama persis di-deploy ke Dokploy.
Aplikasi nggak pernah menyimpan secret di repo. Secret diambil dari HashiCorp Vault lewat Vault Agent.
Ukuran suksesnya saya tulis dari awal, supaya jelas kapan proyek ini dianggap selesai:
PR yang berisi secret, dependency rentan, kode berbahaya, atau Dockerfile jelek terbukti diblokir.
Merge ke
developmentotomatis deploy ke dev. Merge kemaindeploy ke production setelah ada approval.Nggak ada satu pun nilai secret di repo publik.
Orang lain bisa mengulang setup ini dari README tanpa harus tanya saya.
Saya juga tulis anti-goal, yaitu hal yang membuat proyek ini dianggap gagal walaupun "jalan":
Pipeline bisa dilewati, misalnya admin bisa push langsung ke
main.Secret muncul di log CI.
AppRole demo bisa membaca secret milik aplikasi lain.
Gambaran besar
Alur branch-nya seperti ini:
feature/* ──PR──▶ development ──PR──▶ main │ │ │ │ [PR checks] │ push │ push │ test, gitleaks, ▼ ▼ │ semgrep, [deploy.yml] [deploy.yml] │ govulncheck, build image cari image dev │ trivy config, trivy image ⏸ approval manual │ trivy image SBOM (Syft) promosi ke production │ push GHCR smoke test │ deploy dev │ smoke test ▼gagal = tidak bisa mergeUntuk secret saat aplikasi jalan:
Dokploy (env: VAULT_ROLE_ID, VAULT_SECRET_ID) → container vault-agent login AppRole ke Vault → render template → config.json di volume tmpfs (RAM) → container app membaca config.json (read-only)Dan untuk secret yang dipakai CI waktu deploy, ada dua tahap:
Tahap A (Bagian 8) : GitHub Environment Secret DOKPLOY_API_KEYTahap B (Bagian 10): GitHub OIDC token → Vault (JWT auth) → token DokployTahap A itu sengaja dibuat dulu supaya pipeline-nya jalan. Di bagian terakhir, secret di GitHub itu saya hapus dan diganti OIDC, jadi GitHub nggak menyimpan kredensial apa pun.
Keputusan yang saya ambil di awal
Topik | Keputusan |
|---|---|
Aplikasi | Project baru, Go, sangat sederhana |
Repo | Publik. Ruleset untuk blokir merge, tab Security / code scanning, dan approval environment itu gratis hanya untuk repo publik |
Branch |
|
Alur |
|
Registry | GHCR (GitHub Container Registry), gratis |
Deploy | Dokploy, tipe Compose. Auto-deploy Dokploy dimatikan: hanya CI yang boleh memicu deploy |
Secret runtime | Vault Agent sidecar yang me-render template ke file |
Isolasi environment | AppRole dan policy terpisah untuk dev dan production. Role dev nggak bisa baca secret production |
Tools | Semua gratis atau open source |
Ada juga yang sengaja nggak saya kerjakan sekarang, supaya scope-nya nggak melebar:
Tidak dikerjakan | Alasan |
|---|---|
Snyk | Bukan open source dan kuota gratisnya terbatas |
Cosign (signing image), deploy pakai digest | Itu proyek tersendiri: container supply-chain security |
DAST (OWASP ZAP) | Nanti, setelah environment dev stabil |
Kubernetes, AWS, Terraform | Proyek lain |
Database | Nggak perlu untuk demo pipeline |
Security gate: kapan PR dianggap gagal
Ini inti proyeknya. Setiap baris di tabel ini nanti jadi required status check, jadi PR nggak bisa di-merge kalau salah satunya merah.
Cek | Tool | Gagal kalau |
|---|---|---|
Format & test |
| Ada file belum diformat, vet error, atau test gagal |
Secret scanning | Gitleaks | Ada secret apa pun |
SAST | Semgrep CE ( | Ada temuan severity ERROR |
Dependency (SCA) | govulncheck | Ada vuln yang benar-benar dipanggil kode |
Dependency baru | dependency-review-action | Dependency baru dengan vuln High/Critical |
Dockerfile | Trivy config | Misconfig HIGH/CRITICAL, misalnya container jalan sebagai root |
Container image | Trivy image | Ada CVE CRITICAL yang sudah ada fix-nya |
SBOM | Syft (CycloneDX JSON) | SBOM gagal dibuat, deploy ikut gagal |
Branch asal ke | workflow | PR ke |
Production | GitHub Environment | Belum di-approve manual |
Smoke test |
| Versi yang jalan beda dengan commit yang di-deploy |
Tips
Build sekali, promosikan ke production
Image hanya dibangun waktu push ke development, dengan tag sha-<commit>. Waktu development di-merge ke main, pipeline nggak build ulang. Ia mencari image sha-<commit> yang sudah jalan di dev, lalu memberi tag production ke image yang sama itu. Jadi yang jalan di production adalah image yang sudah di-scan dan sudah diuji di dev, bukan hasil build baru yang belum pernah dites.
Struktur repo akhirnya
Supaya kebayang, ini bentuk repo waktu semua bagian selesai:
secure-cicd-go/├── cmd/api/main.go # entrypoint├── internal/ # handler, config, jwt, aes├── Dockerfile # multi-stage, non-root, versi base dipatok├── docker-compose.development.yml├── docker-compose.production.yml├── vault-agent/│ ├── Dockerfile│ ├── agent.hcl # auto_auth approle + template│ └── config.json.ctmpl # template: isinya path secret saja├── vault/│ ├── policies/ # policy Vault, ikut di-review lewat PR│ └── roles/ # role jwt-github (Bagian 10)├── .github/workflows/│ ├── pr-checks.yml│ ├── source-branch.yml│ └── deploy.yml├── .gitleaksignore # exception Gitleaks (Bagian 9)├── .trivyignore # exception CVE + tanggal kedaluwarsa├── scripts/check-exceptions.sh # penegak aturan exception└── docs/Peta seri ini
Gambaran dan persiapan tools (artikel ini)
Vault: secret, policy, dan AppRole per environment
Aplikasi Go sederhana yang membaca secret dari file
Vault Agent sidecar dan Docker Compose
Repo GitHub dan workflow PR sebagai security gate
Ruleset yang memblokir merge, plus demo PR yang gagal
Dokploy: environment dev dan production
Workflow deploy: build, scan, SBOM, GHCR, Dokploy
Proses exception untuk temuan security
CI mengambil token Dokploy dari Vault lewat OIDC
Persiapan tools
Sekarang bagian praktiknya. Tujuannya sederhana: semua alat yang dibutuhkan sudah ada di laptop. Saya pakai Mac, jadi contohnya pakai Homebrew.
Cek dulu yang biasanya sudah ada:
go version # saya pakai go1.27docker versiongh --versionLalu pasang Vault CLI dan scanner-nya. Scanner ini nanti jalan di CI, tapi saya pasang juga di lokal supaya bisa mencoba dulu sebelum push. Jauh lebih cepat daripada menunggu CI merah berkali-kali.
brew install hashicorp/tap/vaultbrew install trivy gitleaks semgrep syftLogin GitHub CLI. Ini interaktif, jadi jalankan sendiri di terminal:
gh auth login # pilih GitHub.com → SSH → login via browsergh auth status # harus muncul: Logged in to github.com account <username>Cek hasil: semua perintah ini harus mencetak versi.
vault versiontrivy --versiongitleaks versionsemgrep --versionsyft versionDNS untuk dev dan production
Saya butuh dua domain, satu per environment. Di panel DNS domain kamu, tambahkan dua A record yang mengarah ke IP VPS tempat Dokploy jalan:
Type | Name | Value |
|---|---|---|
A |
|
|
A |
|
|
Propagasi DNS bisa butuh beberapa menit. Cek dengan dig:
dig +short secure-cicd-dev.domainkamu.com # → IP VPSdig +short secure-cicd.domainkamu.com # → IP VPSHTTPS-nya nanti diurus Traefik dan Let's Encrypt di Dokploy, jadi di sini cukup A record saja.
Rangkuman
Di bagian ini belum ada kode. Yang sudah ada:
Tujuan, ukuran sukses, dan anti-goal yang jelas.
Alur branch dan alur secret.
Daftar security gate yang nanti jadi required check.
Tools di laptop dan dua record DNS.
Di Bagian 2, kita mulai dari fondasi paling penting: menyiapkan secret, policy, dan AppRole di Vault, lalu membuktikan bahwa kredensial dev memang nggak bisa membaca secret production.