Resep sudah tertulis dalam tiga berkas; sekarang tangan kita menyentuh mesin. Bagian ini adalah tempat Dockerfile benar-benar dieksekusi: BuildKit membaca resep, menyaring context, menyusun layer, dan menyerahkan image bernama container-web-statis:1.0. Kita menjalankannya dulu secara manual lewat docker run — bukan karena itu cara terbaik, melainkan karena tiap flag terasa kelayakannya saat diketik sendiri — sebelum memindahkan seluruh definisi ke compose.yaml dan menutup dengan satu docker compose up. Di akhir bagian, situs Kopi Nusantara hidup di port 8080 dan siap diuji.
Membangun Image dengan docker build #
Pastikan terminal berada di dalam folder proyek — titik pada akhir perintah berikut menunjuk ke sana sebagai build context:
$ docker build -t container-web-statis:1.0 .Output yang Dihasilkan:
[+] Building 4.0s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 686B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 91B 0.0s
=> [internal] load metadata for docker.io/library/nginx:1.29-alpine 1.2s
=> [auth] nginx:1.29-alpine pull token from registry-1.docker.io 0.8s
=> [internal] load build context 0.1s
=> => transferring context: 3.982kB 0.0s
=> [1/6] FROM docker.io/library/nginx:1.29-alpine 0.0s
=> => resolve docker.io/library/nginx:1.29-alpine 1.2s
=> [2/6] LABEL org.opencontainers.image.title="container-web-statis" ... 0.0s
=> [3/6] COPY nginx.conf /etc/nginx/conf.d/default.conf 0.1s
=> [4/6] COPY site/ /usr/share/nginx/html 0.0s
=> [5/6] EXPOSE 80 0.0s
=> [6/6] RUN nginx -t 0.2s
=> => # nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
=> => # nginx: configuration file /etc/nginx/nginx.conf test is successful
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:8c4f2a7d19e3b5c7e2f4a91d06b8c3e5d7f2a4b6c8e1d3f5a7b9c2e4d6f8a1b3 0.1s
=> => naming to docker.io/library/container-web-statis:1.0 0.0sBaris LABEL dipotong agar muat — lengkapnya empat pasang kunci-nilai OCI yang kita tulis di Bagian 2. Mari kita telaah anatomi build ini:
[+] Building 4.0s (12/12) FINISHED— ringkasan di puncak: dua belas langkah selesai dalam empat detik. Bandingkan dengan build myapp pada Chapter docker build yang makan 23,9 detik — selisihnya bukan mesin yang lebih cepat, melainkan context yang ramping berkat.dockerignore.load build definition+transferring dockerfile: 686B— BuildKit membaca berkasDockerfilekita sepanjang 686 byte melalui jalurnya sendiri; inilah jalur terpisah yang membuat Dockerfile tetap terbaca meski namanya tersaring — kejutan yang kita bahas di Bagian 2.load .dockerignore+transferring context: 91B— penyaring ditemukan dan ikut tercatat sebagai context; bandingkan dengan2Btanda penyaring kosong pada output-output section Image Build dan Optimisasi.load build context+transferring context: 3.982kB— seluruh bahan yang lolos penyaring:Dockerfile686 byte,.dockerignore91 byte,nginx.conf869 byte,compose.yaml182 byte, dansite/index.html2.154 byte — total 3.982 kilobyte. Angka inilah buah nyata lima baris.dockerignorekita: tanpa penyaring,README.md, materibagian/, dan.gitikut menaikkan angka ini.[1/6] FROMdengan durasi 0.0s — basis tidak diunduh ulang:nginx:1.29-alpinesudah tersimpan lokal dari section-section sebelumnya, dan barisresolve1,2 detik adalah pengesahan bahwa digest lokal masih cocok dengan metadata terbaru — persis perilaku yang kita kenal di docker build.[6/6] RUN nginx -tdengan dua baris keluaran — jangkar keselamatan Bagian 2 bekerja:syntax is okdantest is successfultercetak sebagai bagian dari langkah build. Dua baris kecil ini adalah beda antara image yang gagal di meja kita dan container yang crash di depan pengunjung. Setelahnya,exporting to imagemerakit layer,writing image sha256:8c4f…menetapkan identitas content-addressable, dannaming tomenempel tagcontainer-web-statis:1.0.
Image Baru di Penyimpanan Lokal #
Mari kita sapa image hasil kerja kita:
$ docker imagesOutput yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
container-web-statis 1.0 8c4f2a7d19e3 2 minutes ago 48.2MB
nginx 1.29-alpine 5b1e8c3a9f7d 5 days ago 47.9MBDua baris ini bercerita banyak. Image kita berukuran 48.2MB — hanya setipis di atas 47.9MB milik nginx:1.29-alpine, karena tambahan kita memang cuma dua layer COPY kecil, label, dan metadata; seluruh badan image adalah basis yang diwarisi layer demi layer. Kolom IMAGE ID 8c4f2a7d19e3 adalah dua belas karakter pertama dari sha256:8c4f2a7d19e3… pada output build — dua wajah untuk objek yang sama, persis relasi ID pendek dan ID penuh yang kita kenal di Melihat Image Lokal. Dan CREATED 5 days ago pada baris nginx mengingatkan bahwa kolom itu milik si penerbit image, bukan milik kita — sementara baris pertama benar-benar berumur dua menit karena kita sendiri yang baru membangunnya.
Menjalankan Container Secara Manual #
Image siap; waktunya menghidupkannya. Kita pakai empat flag sekaligus:
$ docker run -d --name web-statis -p 8080:80 container-web-statis:1.0Output yang Dihasilkan:
4d7b2e9a1f38c52a9071e6b8d4f3a2c5e81b7d9f0a3c6e2b1d4f7a8c5e3b2d6fOutput mode detached selalu satu baris: ID container penuh sepanjang 64 karakter heksadesimal. Mari kita bedah perintahnya:
-d(detached) — container berjalan di latar belakang dan terminal langsung kembali ke kita; nginx memang dirancang hidup lama, menahan terminal hanya akan menyandera kita.--name web-statis— nama pilihan sendiri menggantikan nama acak dua kata; seluruh perintah berikutnya cukup menyebutweb-statis.-p 8080:80— menerbitkan port dengan polahost:container: permintaan ke port 8080 di mesin kita diteruskan ke port 80 yang didengarkan nginx di dalam container — port yang sama denganlisten 80dinginx.confdanEXPOSE 80di Dockerfile. Mekanisme lengkapnya adalah wilayah Port Publishing.container-web-statis:1.0— image yang dijalankan, ditulis persis tag hasil build; berbeda dari kebiasaan lama mengetiknginxtelanjang di Menjalankan Container Pertama, kali ini image-nya adalah milik kita sendiri.
Kondisi container bisa dicek sekejap:
$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4d7b2e9a1f38 container-web-statis:1.0 "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp web-statisKolom COMMAND menampilkan "/docker-entrypoint.…" — skrip entrypoint bawaan image nginx yang kita warisi dengan sengaja tidak menulis CMD di Bagian 2. Kolom PORTS mengonfirmasi pemetaan 8080->80 untuk IPv4 dan IPv6, dan STATUS Up 3 minutes berarti proses nginx hidup sebagai PID 1 di dalamnya. Situs sebenarnya sudah bisa dibuka di browser pada port 8080 — tapi tahan dulu: kita pindahkan definisinya ke Compose dulu, lalu menguji semuanya dengan tertib di Bagian 4.
compose.yaml Satu Service #
Empat flag barusan bekerja, tetapi mereka hidup hanya di riwayat terminal — tidak di repositori. Berkas berikut memindahkan seluruhnya menjadi deklarasi:
name: container-web-statis
services:
web:
build: .
image: container-web-statis:1.0
container_name: web-statis
ports:
- "8080:80"
restart: unless-stoppedSepuluh baris ini menggantikan docker build dan docker run kita. Mari kita telaah ketujuh kuncinya:
name: container-web-statis— nama project Compose; network yang dibuat otomatis nanti bernamacontainer-web-statis_defaultmengikuti nama ini, dan tanpacontainer_name, nama itu juga menjadi prefiks nama container.services:danweb:— satu service bernamaweb; nama service adalah identitas resminya di seluruh perintah Compose dan kelak menjadi nama DNS-nya di dalam network — bekal yang baru terasa di proyek multi-service.build: .— service ini dibangun dari Dockerfile pada context folder saat ini; padanan langsung titik pada perintahdocker buildkita.image: container-web-statis:1.0— nama tag untuk hasil build-nya. Kombinasibuilddanimageinilah yang kita kenal di Image, Build, dan Pull: Compose membangun imagenya sendiri lalu menyimpannya dengan nama yang kita tentukan, bukan nama acak.container_name: web-statis— nama container yang sama dengan sesi manual kita; menimpa pola bawaan<project>-<service>-<index>yang kita bedah pada Service.ports:dengan"8080:80"— padanan flag-p, ditulis sebagai daftar. Tanda kutipnya bukan hiasan: YAML generasi lama membaca polaxx:yytanpa kutip sebagai bilangan berbasis 60 — jebakan sexagesimal yang kita bahas tuntas di Port dan Network.restart: unless-stopped— kebijakan bangkit-otomatis dari Restart Policy: container dihidupkan ulang bila prosesnya mati sendiri, tapi keputusan manusia untuk menghentikannya dihormati — nuansa yang kita buktikan langsung di Bagian 4.
Perhatikan pula yang tidak tertulis: kunci version:. Ia sudah obsolete — Compose membaca versi spesifikasinya dari dirinya sendiri — dan menuliskannya hanya menambah kebisingan, persis kebiasaan yang kita buang sejak Pengantar Docker Compose.
Dari docker run ke docker compose up #
Dengan compose.yaml tersimpan, satu perintah seharusnya menyalakan semuanya:
$ docker compose up -d --buildOutput yang Dihasilkan:
Error response from daemon: Conflict. The container name "/web-statis" is already in use by container "4d7b2e9a1f38c52a9071e6b8d4f3a2c5e81b7d9f0a3c6e2b1d4f7a8c5e3b2d6f". You have to remove (or rename) that container to be able to reuse that name.Bukan bug — ini tabrakan yang sehat. Container web-statis dari sesi manual kita masih hidup dan memegang dua hak milik yang tidak bisa dibagi: nama tersebut dan port 8080. Compose ingin menciptakan container barunya dengan container_name dan ports yang sama, dan daemon menolak. Solusinya satu langkah — pamitkan container manual itu:
$ docker rm -f web-statisOutput yang Dihasilkan:
web-statisFlag -f (force) menghentikan lalu menghapus dalam satu perintah — padanan docker stop diikuti docker rm yang kita kenal di Menghentikan dan Menjalankan Container dan Menghapus Container. Sekarang panggung kosong dan perintah yang sama berjalan mulus:
$ docker compose up -d --buildOutput yang Dihasilkan:
[+] Building 1.8s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 686B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 91B 0.0s
=> [internal] load metadata for docker.io/library/nginx:1.29-alpine 1.1s
=> [auth] nginx:1.29-alpine pull token from registry-1.docker.io 0.7s
=> [internal] load build context 0.1s
=> => transferring context: 3.982kB 0.0s
=> CACHED [1/6] FROM docker.io/library/nginx:1.29-alpine 0.0s
=> => resolve docker.io/library/nginx:1.29-alpine 1.1s
=> CACHED [2/6] LABEL org.opencontainers.image.title="container-web-statis" ... 0.0s
=> CACHED [3/6] COPY nginx.conf /etc/nginx/conf.d/default.conf 0.0s
=> CACHED [4/6] COPY site/ /usr/share/nginx/html 0.0s
=> CACHED [5/6] EXPOSE 80 0.0s
=> CACHED [6/6] RUN nginx -t 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:8c4f2a7d19e3b5c7e2f4a91d06b8c3e5d7f2a4b6c8e1d3f5a7b9c2e4d6f8a1b3 0.0s
=> => naming to docker.io/library/container-web-statis:1.0 0.0s
[+] Running 2/2
✔ Network container-web-statis_default Created 0.2s
✔ Container web-statis Started 0.4sBuild kedua ini berjalan di atas jejak build pertama: seluruh langkah Dockerfile tampil CACHED karena tak satu berkas context pun berubah sejak build pertama — nginx -t sekalipun tidak dieksekusi ulang, hanya diingat. Kemudian bagian [+] Running 2/2 menghidupkan stack: network container-web-statis_default diciptakan otomatis, dan container web-statis — kini di bawah pengawasan Compose — menyala. Perintah yang sama akan kita pakai selamanya ke depan: up -d --build membangun bila perlu, memakai cache bila bisa, dan menyala dalam dua detik.
Dua gaya menjalankan situs yang sama kini pernah kita rasa. Tabel ini merangkum padanannya:
| Aspek | docker run manual | Compose |
|---|---|---|
| Nama container | --name web-statis | container_name: web-statis |
| Penerbitan port | -p 8080:80 | ports: ["8080:80"] |
| Kebijakan restart | --restart unless-stopped | restart: unless-stopped |
| Pembangunan image | perintah docker build terpisah | build: . menyatu dengan definisi |
| Eksekusi ulang | mengingat seluruh flag dari riwayat terminal | docker compose up -d --build |
Container hidup di bawah Compose, port 8080 menunggu diketuk, dan seluruh konfigurasi Bagian 2 menahan napas untuk pembuktian. Bagian berikutnya adalah sidangnya: kita membaca sepuluh baris respons curl -I satu per satu, memburu 404 untuk membuktikan try_files, menimbang gzip byte-per-byte, membaca jejak lima permintaan di docker logs, lalu mematikan dan menghidupkan kembali semuanya untuk melihat siapa yang bangkit sendiri. Saatnya membuktikan bahwa semua janji konfigurasi benar-benar ditepati.
Referensi #
- docker buildx build — CLI Reference
— flag-flag
docker buildbeserta perilaku cache-nya. - docker container run
— referensi lengkap flag
-d,--name, dan-p. - Compose file reference
— struktur
compose.yamlbeserta seluruh kunci yang dipakai proyek ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
compose.yaml
Unduh berkasname: container-web-statis
services:
web:
build: .
image: container-web-statis:1.0
container_name: web-statis
ports:
- "8080:80"
restart: unless-stopped
site/index.html
Unduh berkas<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Katalog produk Kopi Nusantara: Torabika, Gayo, dan Kintamani.">
<title>Katalog Produk — Kopi Nusantara</title>
<style>
/* Palet warna terpusat sebagai custom properties. */
:root {
--latar: #f7f4ef;
--teks: #2b2723;
--aksen: #6f4e37;
--kartu: #ffffff;
}
body {
font-family: Georgia, serif;
background: var(--latar);
color: var(--teks);
margin: 0;
line-height: 1.6;
}
header, footer { background: var(--aksen); color: #fff; text-align: center; padding: 1rem; }
h1 { margin: 0; font-size: 1.6rem; }
main { max-width: 680px; margin: 2rem auto; padding: 0 1rem; }
h2 { color: var(--aksen); margin: 0 0 1rem; }
.pengantar { margin: 0 0 1.5rem; }
section.produk { display: grid; gap: 1rem; }
.kartu {
background: var(--kartu);
border: 1px solid #e6dfd3;
border-radius: 8px;
padding: 1rem;
}
.kartu h3 { margin: 0 0 0.25rem; }
.harga { color: var(--aksen); font-weight: bold; margin: 0 0 0.25rem; }
.kartu p { margin: 0; }
</style>
</head>
<body>
<header>
<h1>Kopi Nusantara</h1>
<p>Kopi pilihan Indonesia</p>
</header>
<main>
<h2>Daftar Produk</h2>
<p class="pengantar">Tiga kopi favorit kami, disangrai segar setiap pekan.</p>
<section class="produk">
<article class="kartu">
<h3>Kopi Torabika</h3>
<p class="harga">Rp 28.000</p>
<p>Robusta dengan aroma kuat, favorit pelanggan.</p>
</article>
<article class="kartu">
<h3>Kopi Gayo</h3>
<p class="harga">Rp 45.000</p>
<p>Arabika dataran tinggi Aceh dengan aroma rempah.</p>
</article>
<article class="kartu">
<h3>Kopi Kintamani</h3>
<p class="harga">Rp 42.000</p>
<p>Arabika Bali dengan sentuhan rasa sitrus.</p>
</article>
</section>
</main>
<footer>
<p>© 2026 Kopi Nusantara — disajikan oleh nginx</p>
</footer>
</body>
</html>