Skip to main content
DevOps dari nol · Pelajaran 1 dari 8 · 10 menit

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.
Lihat cara kerjanya

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.

Langkah 1 dari 7. Satu push, dua bagian. GitHub Actions mengubah commit menjadi image Docker; VPS menjalankannya, dan pengunjung sampai ke sana lewat Cloudflare dan nginx.

Tombol: ← → langkah · Home / End · K putar atau jeda

  1. Satu push, dua bagian. GitHub Actions mengubah commit menjadi image Docker; VPS menjalankannya, dan pengunjung sampai ke sana lewat Cloudflare dan nginx.

  2. 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]
  3. 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)
  4. 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 .env
  5. Server 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-orphans
  6. Request 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)
  7. 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)

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)

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)

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)

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.

Cara situs ini melakukannya

Konfigurasi nyata di balik pelajaran ini

Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.

.github/workflows/deploy.yml:1-10
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.

.github/workflows/deploy.yml:185-196
  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: false

needs: 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.

docker-compose.yml:1-4
# 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.

docker-compose.yml:24-26
    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.

Yang sering rusak

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.

Coba sendiri

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.

  1. 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-lab
  2. Ajukan 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)'
  3. 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)'
  4. 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)'
  5. 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/
Kuis singkat

Tiga pertanyaan sebelum lanjut

Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada yang disimpan atau dinilai, jadi tebak saja dengan bebas.

  1. 1.Seorang rekan masuk ke VPS lewat SSH dan mengubah satu nilai di .env server secara manual. Apa yang terjadi pada perubahan itu saat push berikutnya ke main?
  2. 2.Seseorang mengusulkan agar API key disalin ke image Docker saat build, supaya server tidak perlu .env. Apa harga sebenarnya?
  3. 3.Tepat setelah deploy, loop curl Anda terus menampilkan halaman lama, sehingga situs terlihat kembali ke versi sebelumnya. Apa yang sebaiknya Anda periksa lebih dulu?
Pikirkan dulu

Jawab dalam hati, lalu cek

  1. 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. 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. 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. 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.

Pelajari lebih dalam

Bahasan mendalam terkait di situs ini