DevOps Adalah: Alur CI/CD dari Git Push sampai Server Produksi
DevOps adalah cara membawa kode ke production secara andal. Ikuti alurnya: git push, GitHub Actions, image Docker, VPS, nginx, sampai Cloudflare.
Oleh Matthews WongDitinjau pada
Poin utama
DevOps adalah praktik membawa kode dari laptop sampai ke production secara otomatis dan andal. Di situs ini, git push memicu GitHub Actions yang memeriksa kode, mem-build image Docker, lalu menyimpannya di registry; VPS kemudian menarik dan menjalankan image itu, dan pengunjung sampai ke sana lewat Cloudflare dan nginx.
Setelah pelajaran ini, Anda bisa
- Menyebutkan setiap tahap antara git push dan halaman di browser pengunjung, beserta tugas masing-masing.
- Membedakan build time dan run time, serta tahu di mana konfigurasi dan secret masuk.
- Membaca header sebuah response dan menunjukkan lapisan mana yang menghasilkannya.
Ikuti diagramnya langkah demi langkah
Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.
Dari git push sampai halaman di browser
Push menjalankan verify, lalu build-and-push yang menyimpan image di registry, lalu deploy yang membuat VPS menarik dan menjalankannya; pengunjung sampai ke aplikasi lewat Cloudflare dan nginx.
Tombol: ← → langkah · Home / End · K putar atau jeda
Satu push, dua bagian. GitHub Actions mengubah commit menjadi image Docker; VPS menjalankannya, dan pengunjung sampai ke sana lewat Cloudflare dan nginx.
Push ke branch main adalah event yang memicu workflow. Di runner yang baru, job verify meng-install dependensi, menjalankan typecheck, lalu lint.
on: push: branches: [main]build-and-push menunggu verify lewat needs, jadi hanya jalan kalau pemeriksaan lolos. Job ini mem-build image lalu mem-push-nya ke GHCR dengan tag latest dan SHA commit.
needs: verifyTebak langkah berikutnya Di mana secret aplikasi masuk dalam perjalanan ke production? (Ditanam ke image saat build · Ditulis ke .env di server)deploy menunggu build-and-push. Job ini masuk ke VPS lewat SSH dan menulis pengaturan runtime ke .env dari GitHub secrets; tidak ada secret yang tertanam di image.
chmod 600 .envServer menarik image baru, lalu docker compose up -d mengganti container yang sedang berjalan dengan container dari image itu. Pergantian itulah deploy-nya.
docker compose pull docker compose up -d --remove-orphansRequest pengunjung sampai di Cloudflare lebih dulu, lalu ke nginx di VPS, yang menangani TLS dan meneruskannya ke container yang hanya mendengarkan di localhost.
# Bind to loopback only — nginx terminates TLS and proxies to this port.Tebak langkah berikutnya Berikutnya, file JavaScript ber-hash diminta. Siapa yang menjawabnya? (Cloudflare, dari cache-nya · nginx di VPS · Container Next.js)File statis ber-hash, seperti chunk JavaScript, langsung dilayani dari cache Cloudflare. HTML selalu diteruskan ke server, dan itu disengaja: URL yang sama juga bisa menjawab dalam Markdown.
cache-control: public, max-age=31536000, immutable cf-cache-status: HIT
Apa itu pipeline DevOps?
DevOps adalah praktik membawa kode dari laptop ke production dengan andal, lalu menjaganya tetap berjalan. Pipeline adalah bagian otomatis dari praktik itu: rangkaian langkah tetap yang dilalui setiap perubahan, dengan urutan yang sama, tanpa ada orang yang menyalin file ke server secara manual. CI (continuous integration) memeriksa setiap perubahan. CD (continuous delivery atau deployment) mengirim perubahan yang sudah lolos.
Baca selengkapnya (1)Tutup
Di situs ini, pipeline-nya adalah satu workflow GitHub Actions, yang didefinisikan GitHub sebagai proses otomatis yang bisa dikonfigurasi dan menjalankan satu atau beberapa job. Push ke branch main adalah event yang memicunya, dan setiap job berjalan di runner, yaitu server yang mengeksekusi workflow. Secara default job berjalan paralel, jadi urutannya datang dari needs: build-and-push membutuhkan verify, dan deploy membutuhkan build-and-push. Kalau satu job gagal, semua job yang membutuhkannya dilewati.
Apa beda build time dan run time?
Build time terjadi sekali untuk setiap commit, di runner GitHub. Job verify menjalankan typecheck dan lint. Lalu build-and-push mengubah kode menjadi image Docker, yaitu template read-only berisi aplikasi yang sudah di-build beserta semua yang dibutuhkannya untuk berjalan, dan mem-push-nya ke GitHub Container Registry di ghcr.io. Setiap image diberi dua tag, latest dan SHA commit, dan deploy menjalankan tag SHA, jadi Anda selalu tahu commit mana yang sedang live.
Baca selengkapnya (1)Tutup
Run time terjadi di VPS. Job deploy masuk lewat SSH, menulis file .env dari GitHub secrets dan variables, lalu menyuruh Docker Compose menarik image baru dan mengganti container yang sedang berjalan dengan container dari image itu. Tidak ada secret yang tertanam di image, jadi image yang sama bisa dijalankan di mana saja. Konsekuensinya, GitHub menjadi sumber kebenaran untuk konfigurasi: file .env di server ditulis ulang setiap kali deploy.
Bagaimana request pengunjung sampai ke aplikasi?
Jalur penyajian berjalan ke arah sebaliknya. Record DNS situs ini di-proxy oleh Cloudflare, jadi browser yang mencari nama domain mendapat alamat Cloudflare, bukan alamat server. Edge Cloudflare menjawab dari cache-nya atau meneruskan request ke server. Di server, nginx menangani TLS lalu meneruskan request ke container, yang hanya dipublikasikan di 127.0.0.1, jadi tidak ada yang bisa mengakses aplikasi langsung dari luar mesin.
Baca selengkapnya (1)Tutup
Setiap lapisan meninggalkan jejak di header response. server: cloudflare berarti response itu lewat Cloudflare. cf-cache-status menunjukkan apa yang dilakukan edge: HIT berarti diambil dari cache Cloudflare, dan DYNAMIC berarti request diteruskan ke server tanpa memeriksa cache. Di situs ini, file JavaScript ber-hash kembali dengan HIT dan header cache immutable satu tahun, sedangkan HTML selalu DYNAMIC, karena URL yang sama juga bisa mengembalikan Markdown untuk klien yang memintanya.
Apa saja yang dibahas di sisa kursus DevOps ini?
Setiap titik di peta ini punya pelajarannya sendiri. Pertama server Linux-nya: login dengan SSH key alih-alih password, firewall, dan port mana yang perlu terbuka. Lalu tiga pelajaran tentang Docker: bagaimana image dibangun lapis demi lapis, bagaimana multi-stage build menjaga ukurannya tetap kecil, dan bagaimana Docker Compose menjalankannya di production dengan batas memori, health check, dan restart policy.
Baca selengkapnya (1)Tutup
Setelah itu pipeline GitHub Actions secara rinci: job verify, build-and-push, dan deploy, serta di mana secret disimpan. Kemudian nginx, TLS, dan Cloudflare, dengan aturan apa yang boleh di-cache di edge dan apa yang tidak boleh sama sekali. Kursus ditutup dengan jaring pengaman untuk satu server: monitoring, rollback rilis yang bermasalah, dan backup.
Konfigurasi nyata di balik pelajaran ini
Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.
name: Deploy to VPS
# Build a Docker image, push it to GHCR, then deploy it to the VPS over SSH.
# All app configuration is read from GitHub at run time:
# Secrets → GROQ_API_KEY, RESEND_API_KEY, VPS_SSH_HOST, VPS_SSH_USER, VPS_SSH_KEY
# Variables→ CONTACT_FROM_EMAIL, CONTACT_TO_EMAIL, APP_PORT, DEPLOY_PATH, VPS_SSH_PORT
on:
push:
branches: [main]
workflow_dispatch:Pemicunya: push ke main, atau run manual dari tab Actions (workflow_dispatch). Header-nya hanya mencantumkan nama secret dan variable yang dibaca pipeline saat run time. Nilainya disimpan di pengaturan GitHub repository, tidak pernah di dalam kode.
deploy:
name: Deploy to VPS
needs: build-and-push
runs-on: ubuntu-latest
# The one group that must NEVER cancel in progress. This step SSHes to the
# VPS and runs `docker compose pull && up -d`; interrupting it can leave the
# box between images. Concurrent deploys queue and run one at a time, which
# is the original intent — it is only the build chain above that should be
# throwing away superseded work.
concurrency:
group: deploy-vps
cancel-in-progress: falseneeds: build-and-push yang membuat deploy menunggu, dan build-and-push menulis needs: verify dengan cara yang sama. Concurrency group adalah separuh lain dari desainnya: push yang lebih baru membatalkan build yang sudah basi, tetapi deploy yang sudah mulai tidak pernah diinterupsi, karena berhenti di tengah jalan bisa meninggalkan server di antara dua image.
# Runtime compose used on the VPS. The image is built + pushed to GHCR by
# .github/workflows/deploy.yml; this file is copied to the server, and CI
# writes a sibling .env (from GitHub Secrets/Variables) that supplies both the
# ${IMAGE}/${APP_PORT} substitutions below and the container's runtime env.Kontrak antara build time dan run time, dalam empat baris. CI mem-build image dan menyalin file ini ke server, lalu menulis .env di sebelahnya. Compose membaca .env itu untuk dua hal: tag image dan port di file ini, serta environment container.
ports:
# Bind to loopback only — nginx terminates TLS and proxies to this port.
- "127.0.0.1:${APP_PORT:-3002}:3000"Aplikasi mendengarkan di port 3000 di dalam container, dan host hanya mempublikasikannya di 127.0.0.1. nginx di mesin yang sama bisa menjangkaunya; internet tidak bisa. Tanpa awalan 127.0.0.1, Docker melakukan bind ke semua interface, yang bisa membuat container terbuka langsung ke internet.
Kesalahan yang benar-benar membuang waktu
Menilai deploy dari salinan yang ter-cache
Loop polling dengan curl biasa bisa terus mendapat salinan dari sebelum deploy. Pada 2026-09-24 hal itu membuat kesimpulan yang salah di sini: situs terlihat seperti kembali ke versi lama, padahal build baru sudah melayani. Tambahkan query string acak di setiap pengecekan, dan baca cf-cache-status sebelum memercayai yang Anda lihat.
Edit manual di server hilang begitu saja
Job deploy menulis ulang .env dari GitHub secrets setiap kali berjalan, jadi nilai yang diketik seseorang langsung di .env server akan hilang pada push berikutnya. Ubah konfigurasi di pengaturan GitHub, lalu deploy. Container yang sedang berjalan juga tetap memakai environment saat ia dijalankan sampai container itu dibuat ulang.
Secret tertanam di dalam image
API key yang ditulis ke image saat build ikut terbawa ke setiap salinannya: registry, setiap server yang menariknya, dan setiap laptop yang menjalankannya. Pipeline ini sama sekali tidak memasukkan secret ke image, melainkan menuliskannya di server saat run time.
Yang akan saya ubah di sini: tunggu health check
docker compose up -d langsung selesai begitu container menyala, sebelum health check-nya lolos. Jadi rilis yang menyala lalu gagal health check tetap membuat workflow berwarna hijau. docker compose up --wait menunggu sampai service berjalan atau sehat, dan kalau penantian itu gagal, job deploy ikut gagal.
Lakukan di komputer Anda sendiri
Anda butuh terminal dengan curl, dan Docker untuk langkah pertama. Tidak perlu akun apa pun. Anda akan membangun bagian paling bawah peta ini di laptop, lalu membaca header situs ini untuk menemukan setiap lapisan di atasnya.
Jalankan nginx di container yang hanya dipublikasikan di localhost, lalu tanyakan siapa yang menjawab. Header Server menyebut nginx: ini satu container yang hanya bisa dijangkau dari mesin Anda sendiri.
docker run -d --rm --name map-lab -p 127.0.0.1:8080:80 nginx:alpine curl -sI http://127.0.0.1:8080/ | grep -i '^server' docker rm -f map-labAjukan pertanyaan yang sama ke situs ini. Hasilnya server: cloudflare dan cf-cache-status: DYNAMIC, karena HTML-nya melewati edge sampai ke server.
curl -sI https://www.matthewswong.com/en/ \ | grep -iE '^(server|cf-cache-status|cache-control|strict-transport-security|vary)'Minta URL yang sama dalam format Markdown. Content type-nya berubah dan response membawa Vary: Accept: satu URL, dua isi, dan itulah alasan edge tidak boleh men-cache HTML ini.
curl -sI -H 'Accept: text/markdown' https://www.matthewswong.com/en/ \ | grep -iE '^(content-type|vary|cf-cache-status)'Cari file JavaScript ber-hash di halaman itu dan bandingkan header-nya. Hasilnya seharusnya public, max-age=31536000, immutable, biasanya dengan cf-cache-status: HIT.
curl -s https://www.matthewswong.com/en/ | grep -oE '/_next/static/[^"]+\.js' | head -1 curl -sI "https://www.matthewswong.com<paste-the-path-here>" | grep -iE '^(cf-cache-status|cache-control)'Lihat ke mana waktu habis dalam satu request: DNS, koneksi TCP, TLS handshake, dan byte pertama. Lalu gambar peta ini dari ingatan dan tandai titik mana yang menghasilkan setiap header yang Anda lihat.
curl -o /dev/null -s -w 'dns %{time_namelookup}s connect %{time_connect}s tls %{time_appconnect}s first byte %{time_starttransfer}s\n' \ https://www.matthewswong.com/en/
Tiga pertanyaan sebelum lanjut
Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada yang disimpan atau dinilai, jadi tebak saja dengan bebas.
Jawab dalam hati, lalu cek
1.Job verify gagal pada sebuah commit. Apa yang sampai ke server?Lihat jawabannya
Jawaban:Tidak ada. build-and-push membutuhkan verify dan deploy membutuhkan build-and-push, dan GitHub melewati setiap job yang membutuhkan job yang gagal. Situs tetap menjalankan image terakhir yang baik.
2.Kenapa aplikasi dipublikasikan di 127.0.0.1, bukan di semua interface?Lihat jawabannya
Jawaban:Supaya hanya nginx, di mesin yang sama, yang bisa menjangkaunya. Referensi Compose dari Docker memperingatkan bahwa tanpa host IP, Docker melakukan bind ke semua interface, yang bisa membuat container terbuka langsung ke internet.
3.Anda mengubah sebuah secret di pengaturan GitHub. Kapan situs yang sedang berjalan melihatnya?Lihat jawabannya
Jawaban:Pada deploy berikutnya. Job deploy menulis .env dari secret setiap kali berjalan dan membuat ulang container; sampai saat itu, container yang sedang berjalan tetap memakai environment saat ia dijalankan.
4.File .js mengembalikan cf-cache-status: HIT dan HTML mengembalikan DYNAMIC. Mesin mana yang menghasilkan masing-masing response?Lihat jawabannya
Jawaban:File .js berasal dari cache edge Cloudflare dan tidak pernah sampai ke server. HTML diteruskan ke VPS, tempat nginx meneruskannya ke container Next.js.
Bahasan mendalam terkait di situs ini
Sumber
Sumber primer yang diperiksa pada tanggal riset. Ikuti tautannya untuk versi terbaru.
- Dokumentasi GitHub: memahami GitHub Actions
- Dokumentasi GitHub: sintaks workflow (on, needs)
- Dokumentasi GitHub: bekerja dengan Container registry
- Dokumentasi Docker: gambaran umum Docker (image dan container)
- Dokumentasi Docker: referensi service Compose (ports, env_file)
- Dokumentasi Docker: docker compose up
- Dokumentasi Cloudflare: cara kerja Cloudflare
- Dokumentasi Cloudflare: respons cache (cf-cache-status)
- Manual curl (-I, -H, dan -w)