Home / DevOps, Cloud, and Developer Tools / Docker dari Nol: …

Docker dari Nol: Install, Build Image, Jalankan Container, Volume, dan Network

Memasang Docker memang cuma satu baris perintah, tetapi image versus container, lapisan data yang hilang saat container dihapus, dan perbedaan jaringan bawaan dengan jaringan buatan sendiri adalah tempat pemula tersesat. Tutorial ini menjalankan seluruh perintahnya di satu mesin Ubuntu dan menampilkan output aslinya, sehingga yang Anda baca di sini adalah hasil yang bisa Anda ulang, bukan asumsi.

Ringkasan

  • Image bersifat immutable dan tersusun dari lapisan read-only; container adalah proses terisolasi dengan lapisan tulis sendiri.
  • Data di lapisan container hilang bersama container; volume bernama bertahan setelah container dihapus.
  • -p 8080:80 mempublikasikan port ke semua alamat host, bukan hanya localhost.
  • Jaringan bridge bawaan tidak me-resolve nama container; jaringan buatan memberi DNS di 127.0.0.11.
  • Multi-stage build memangkas image Go dari 364 MB menjadi 25,8 MB pada mesin uji.
  • .dockerignore membatasi apa yang dikirim ke builder dan menentukan efektivitas cache.

Prasyarat dan Lingkungan Pengujian

Semua perintah dan output di bawah berasal dari mesin uji berikut, bukan dari dokumentasi semata.

KomponenNilai TerujiKeterangan
Sistem OperasiUbuntu 26.04.1 LTSArsitektur x86_64
Docker Engine29.8.1Klien dan daemon versi sama
Docker Composev5.5.1Plugin docker-compose-plugin
Storage driveroverlayfsDockerRootDir di /var/lib/docker
Cgroupsystemd, cgroup v2Terlihat pada docker info
Image ujinginx:alpine 94,4 MB, alpine:latest 13 MB, golang:1.26-alpine 364 MBNginx 1.31.6, Alpine 3.24.2

Perintah dan nama paket di artikel ini mengikuti dokumentasi resmi untuk Ubuntu 26.04, 24.04, dan 22.04 LTS. Pada distribusi lain, ganti repositori dan nama paket sesuai panduan masing-masing. Halaman panduan instalasi resmi Docker untuk Ubuntu adalah sumber acuan untuk langkah di bawah.

Langkah 1: Pasang Docker Engine dari Repository Resmi

Langkah ini memasang Docker Engine beserta CLI, containerd, Buildx, dan Compose dari repository resmi, lalu memverifikasi bahwa daemon berjalan.

  • Tambahkan kunci GPG resmi Docker. Kunci disimpan di direktori khusus agar tidak menimpa keyring sistem:
bash
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
  • Daftarkan repository dengan format deb822. Docker sekarang memakai berkas docker.sources, bukan lagi docker.list yang dipakai tutorial lama. Tulis nama suite dari /etc/os-release agar otomatis cocok dengan rilis Anda:
bash
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
  • Hapus paket Docker lama yang konflik, lalu pasang yang baru. Paket bawaan distribusi (.docker.io) dan paket Compose v1 tidak boleh bercampur dengan paket resmi:
bash
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
PaketFungsi
docker-ceDaemon Docker Engine
docker-ce-cliPerintah docker
containerd.ioRuntime container yang dipakai Engine
docker-buildx-pluginBuilder BuildKit di balik docker build
docker-compose-pluginCompose v2 sebagai plugin
  • Verifikasi daemon dan jalankan container pertama. hello-world adalah uji pemasangan paling jujur, karena benar-benar menyentuh daemon:
bash
sudo systemctl status docker
sudo docker run --rm hello-world

Keluaran hello-world merangkum seluruh alur kerja Docker dalam empat langkah: klien menghubungi daemon, daemon menarik image dari Docker Hub, daemon membuat container dari image itu, lalu mengalirkan output-nya kembali ke terminal Anda. Saya memakai --rm agar container percobaan hilang otomatis beserta volume anonimnya.

  • Berikan hak akses tanpa sudo (opsional). Grup docker memberi hak setara root, jadi dokumentasi resmi memberi peringatan tegas soal ini:
bash
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

Memberikan akses ke grup docker sama dengan memberi hak administratif penuh, karena pengguna dapat menjalankan container dengan hak root. Pada mesin produksi atau server bersama, biarkan sudo docker dipakai seperlunya, atau batasi siapa saja yang boleh masuk ke grup tersebut.

Langkah 2: Bedakan Image dan Container

Langkah ini menunjukkan batas yang sering disalahpahami: image adalah paket read-only, sedangkan container adalah proses berjalan dengan lapisan tulisnya sendiri.

  • Tarik image dan lihat daftar image di mesin Anda. Perhatikan format keluaran Docker 29 yang memisahkan ukuran disk dan ukuran konten:
bash
docker pull nginx:alpine
docker image ls
text
IMAGE                ID             DISK USAGE   CONTENT SIZE   EXTRA
alpine:latest        294b683cb724         13MB         3.94MB
hello-world:latest   5e2309035332       25.9kB         9.49kB
nginx:alpine         df221db836e1       94.4MB         27.2MB
  • Baca lapisan image dari atas ke bawah. docker image history menunjukkan instruksi yang membangun image. Dua baris teratas adalah lapisan yang saya tambahkan sendiri; sisanya warisan dari image resmi nginx:
bash
docker image history nginx:alpine
text
IMAGE          CREATED          CREATED BY                         SIZE      COMMENT
f8a68025a558   About a minute   EXPOSE [80/tcp]                    0B        buildkit.dockerfile.v0
<missing>      About a minute   COPY index.html /usr/share/ngin…   24.6kB    buildkit.dockerfile.v0
<missing>      4 days ago       RUN /bin/sh -c set -x && apkArch=…  52.4MB    buildkit.dockerfile.v0
<missing>      4 days ago       CMD ["nginx" "-g" "daemon off;"]   0B        buildkit.dockerfile.v0
  • Buktikan bahwa lapisan tulis ikut hilang bersama container. Ini bukan teoretis; saya menulis berkas di dalam container, menghapus container-nya, lalu container baru dengan image yang sama tidak menemukan berkas tersebut:
bash
docker run --name layer-test -d alpine sleep 300
docker exec layer-test sh -c 'echo "data sementara" > /tmp/ujicoba.txt'
docker rm -f layer-test
docker run --rm alpine ls -l /tmp/ujicoba.txt
text
ls: /tmp/ujicoba.txt: No such file or directory
AspekImageContainer
SifatImmutable, tidak bisa diubah setelah dibuatBerjalan, bisa dihapus
IsiLapisan read-only berisi berkas, biner, pustaka, konfigurasiLapisan tulis di atas image, unik per container
JumlahSatu image dipakai banyak containerSatu container merujuk satu image
Daur hidupBertahan sampai dihapus eksplisitHilang saat container dihapus, kecuali datanya di volume

Container bukan virtual machine ringan. Menurut dokumentasi resmi, “A container is simply an isolated process with all of the files it needs to run. If you run multiple containers, they all share the same kernel.” Semua container berbagi kernel host, dan proses container berjalan di namespace, cgroup, serta filesystem union milik container tersebut. Detail mekanisme lapisan ada di halaman storage.

Langkah 3: Build Image Sendiri dengan Dockerfile

Langkah ini membangun image web statis dari nol, lalu memperagakan secara konkret kapan cache build berlaku dan kapan ia dibatalkan.

  • Buat konteks build berisi satu halaman HTML. Dockerfile hanya instruksi; semua berkas yang dirujuk harus ada di dalam konteks build:
bash
mkdir -p website && cd website
printf '%s\n' '<!DOCTYPE html>' \
  '<html lang="id">' \
  '<head><meta charset="utf-8"><title>Demo Docker</title></head>' \
  '<body><h1>Hola dari image Docker</h1></body>' \
  '</html>' > index.html
  • Tulis Dockerfile tiga baris. FROM memilih basis, COPY menaruh berkas, EXPOSE hanya mendokumentasikan port dan tidak membukanya di host:
bash
cat > Dockerfile <<'DOCKERFILE'
FROM nginx:alpine

COPY index.html /usr/share/nginx/html/index.html

EXPOSE 80
DOCKERFILE
InstruksiFungsiCatatan
FROMMenentukan image dasar dan memulai stage baruPin versi atau digest agar hasil reproducible
COPYMenyalin berkas dari konteks buildLebih disukai daripada ADD untuk menyalin antar stage
RUNMenjalankan perintah saat build, hasilnya jadi lapisanGabung beberapa RUN agar cache tidak terpecah
EXPOSEDokumentasi port yang didengarkanTidak membuka port di host
USERMenentukan user non-root saat container berjalanPraktik keamanan dasar
CMDPerintah default saat container berjalanBentuk exec: CMD ["halo"]
  • Build image dan beri nama tag. Flag -t menamai hasil build agar bisa dijalankan tanpa menyebut ID panjang:
bash
docker build -t demo-web:latihan .
  • Buktikan cache bekerja saat tidak ada perubahan. Build kedua memakai kembali lapisan yang sama, sehingga instruksi COPY tampil sebagai CACHED:
bash
docker build -t demo-web:latihan .
text
#5 [1/2] FROM docker.io/library/nginx:alpine@sha256:df221db836e1…
#5 CACHED
#6 [2/2] COPY index.html /usr/share/nginx/html/index.html
#6 CACHED
  • Ubah isi berkas, lalu build lagi. Lapisan FROM tetap dari cache, lapisan setelah COPY dibangun ulang. Sesuai aturan invalidasi cache, “Once the cache is invalidated, all subsequent Dockerfile commands generate new images”:
bash
printf '%s\n' '<h1>Hola dari image Docker (v2)</h1>' >> index.html
docker build -t demo-web:latihan .
text
#5 [1/2] FROM docker.io/library/nginx:alpine@sha256:df221db836e1…
#5 CACHED
#6 [2/2] COPY index.html /usr/share/nginx/html/index.html
#6 DONE 0.0s

Sentuh berkas tanpa mengubah isi (touch index.html) tetap menghasilkan #6 CACHED. Dokumentasi resmi menegaskan bahwa untuk ADD dan COPY, “The modification time of a file (mtime) is not taken into account”. Yang diperiksa adalah checksum isi berkas, bukan waktu modifikasinya.

  • Batasi apa yang dikirim ke builder dengan .dockerignore. Tanpa berkas ini, seluruh konteks ikut terkirim dan bisa ikut ter-cache bersama COPY . .:
bash
printf '%s\n' 'rahasia.txt' '*.log' > .dockerignore
docker build -t demo-web:latihan . 2>&1 | grep 'transferring context'

Ukuran konteks turun dari 224 byte menjadi 32 byte setelah rahasia.txt dikecualikan. Dokumentasi konteks build menjelaskan bahwa aturan .dockerignore berlaku untuk seluruh subdirektori, ** didukung, dan baris yang cocok terakhir yang menang.

Langkah 4: Perkecil Image dengan Multi-Stage Build

Langkah ini memisahkan tahap kompilasi dari tahap runtime, sehingga toolchain build tidak ikut terbawa ke image akhir.

  • Tulis program Go minimal dan Dockerfile dua tahap. Tahap pertama membawa compiler; tahap kedua hanya menerima berkas binary hasil build:
bash
mkdir -p halo && cd halo
cat > main.go <<'GO'
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	addr := ":8080"
	if p := os.Getenv("PORT"); p != "" {
		addr = ":" + p
	}
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Halo dari container Go\n")
	})
	log.Fatal(http.ListenAndServe(addr, nil))
}
GO
bash
cat > Dockerfile <<'DOCKERFILE'
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY main.go ./
RUN go mod init halo && go build -o /out/halo .

FROM alpine:3.22
COPY --from=build /out/halo /halo
EXPOSE 8080
USER 65534:65534
CMD ["/halo"]
DOCKERFILE
  • Build sekali, bandingkan ukuran image. Perbedaan pada mesin uji ini dapat diperiksa langsung:
bash
docker build -t halo-app:latihan .
docker image ls --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' | grep -E 'halo-app|golang:1.26'
text
halo-app:latihan	25.8MB
golang:1.26-alpine	364MB

Image final sekitar 14 kali lebih kecil karena tidak membawa compiler Go. Menurut panduan multi-stage build, setiap instruksi FROM memulai stage baru, dan hanya lapisan yang dibutuhkan oleh stage tujuan yang ikut terpasang di image akhir.

  • Jalankan image hasil dan pastikan bukan root. USER 65534:65534 membuat proses berjalan sebagai nobody:
bash
docker run -d --name halo-app -p 8082:8080 halo-app:latihan
curl -sS http://localhost:8082/
docker exec halo-app id
text
Halo dari container Go
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
  • Bangun stage tertentu saja saat menelusuri masalah. --target build menghentikan build di stage kompilasi tanpa mengemas image akhir. BuildKit hanya membangun stage yang dibutuhkan tahap tersebut:
bash
docker build --target build -t halo-app:build-stage .
docker image ls --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' | grep halo-app
text
halo-app:latihan	25.8MB
halo-app:build-stage	489MB

Image build-stage justru lebih besar karena masih memuat toolchain. Gunakan ini untuk debugging, bukan untuk produksi.

Langkah 5: Jalankan Container dan Publikasikan Port

Langkah ini menjalankan web server di dalam container dan memperlihatkan secara konkrem perbedaan mempublikasikan port ke semua alamat host dengan hanya ke localhost.

  • Jalankan container dalam mode detached. Flag -d mengembalikan kontrol ke terminal, --name memberi nama yang mudah diketik, dan -p memetakan port:
bash
docker run -d --name web-nginx -p 8080:80 nginx:alpine
  • Periksa status dan uji dari host. Output docker ps langsung menunjukkan pemetaan port, lalu curl membuktikan layanannya benar-benar menjawab:
bash
docker ps
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8080/
text
CONTAINER ID   IMAGE          COMMAND                 CREATED         STATUS         PORTS                                     NAMES
a2050b359dba   nginx:alpine   "/docker-entrypoint.…"   2 seconds ago   Up 2 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   web-nginx
HTTP 200
  • Perhatikan ke mana port sebenarnya terikat. docker port memperlihatkan ikatannya, dan ss mengonfirmasi socket mana yang benar-benar terbuka di host:
bash
docker port web-nginx
ss -ltn | grep 8080
text
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
LISTEN 0      4096         0.0.0.0:8080       0.0.0.0:*
LISTEN 0      4096            [::]:8080          [::]:*

-p 8080:80 memetakan port 8080 pada semua alamat host (0.0.0.0 dan [::]), sehingga layanan bisa dijangkau dari jaringan lain. Dokumentasi resmi menyebutnya “insecure by default”. Untuk layanan yang hanya perlu diakses dari host, tulis alamatnya secara eksplisit dan verifikasi sendiri hasilnya:

bash
docker run -d --name halo-local -p 127.0.0.1:8083:8080 halo-app:latihan
ss -ltn | grep 8083
text
LISTEN 0      4096       127.0.0.1:8083       0.0.0.0:*
Bentuk -pArti
-p 8080:80Port 8080 di semua alamat host ke port 80 container
-p 127.0.0.1:8080:80Hanya host yang bisa mengakses
-p 8080:80/udpPublish protokol UDP
-PPublish semua port yang di-EXPOSE ke port acak
  • Baca log dan masuk ke dalam container yang sedang berjalan. Gunakan docker exec tanpa -it untuk perintah satu kali; -it hanya diperlukan bila Anda ingin shell interaktif:
bash
docker logs web-nginx
docker exec web-nginx nginx -v
  • Hentikan lalu hapus container. Menghapus image adalah langkah terpisah; menghapus container tidak menyentuh image maupun volume bernama:
bash
docker stop web-nginx
docker rm web-nginx
Flag docker runFungsi
-d, --detachJalankan di latar belakang
--nameBeri nama container
-p, --publishPublikasikan port container ke host
-e, --envSet variabel lingkungan container
--rmHapus container beserta volume anonimnya saat berhenti
--networkTentukan jaringan yang dipakai
--restartKebijakan mulai ulang: no, always, unless-stopped, on-failure

Langkah 6: Simpan Data di Luar Container dengan Volume

Langkah ini membuktikan perlakuan data yang berbeda antara lapisan container, volume bernama, dan bind mount.

  • Buat volume bernama dan isi dari container pertama. Volume dibuat otomatis jika belum ada, dan isinya bertahan di luar daur hidup container:
bash
docker volume create data-app
docker run --rm --mount type=volume,src=data-app,dst=/data \
  alpine sh -c 'echo "halo dari container" > /data/berkas.txt'
  • Baca kembali dari container kedua yang berbeda. Berkas masih ada karena volume, bukan lapisan container, yang menyimpannya:
bash
docker run --rm --mount type=volume,src=data-app,dst=/data alpine cat /data/berkas.txt
docker volume inspect data-app --format 'Name={{.Name}} Driver={{.Driver}} Mountpoint={{.Mountpoint}}'
text
halo dari container
Name=data-app Driver=local Mountpoint=/var/lib/docker/volumes/data-app/_data
  • Periksa mount yang terpasang pada container. Bagian Mounts di docker inspect memberi tahu sumber, tujuan, dan mode baca-tulis:
bash
docker inspect --format '{{json .Mounts}}' vol-test
text
[{"Type":"volume","Name":"data-app","Source":"/var/lib/docker/volumes/data-app/_data","Destination":"/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
  • Bandingkan dengan bind mount. Pada bind mount, berkas yang ditulis container langsung muncul di direktori host:
bash
mkdir -p bind-test && echo "isi dari host" > bind-test/izin.txt
docker run --rm --mount type=bind,src=$PWD/bind-test,dst=/mnt \
  alpine sh -c 'echo "ditulis container" > /mnt/dari-container.txt'
ls -l bind-test/
text
-rw-r--r-- 1 root   root   19 Sep 27 17:05 dari-container.txt
-rw-rw-r-- 1 <user> <user> 14 Sep 27 17:05 izin.txt

Berkas yang dibuat container milik root, sedangkan berkas bawaan host tetap milik pengguna host; <user> di atas ditulis sebagai placeholder.

AspekVolume bernamaBind mount
SumberDikelola Docker di /var/lib/docker/volumes/Direktori milik host yang Anda tentukan
Bertahan setelah container dihapusYa, kecuali docker volume rmYa, karena berkasnya milik host
Akses dari hostLebih merepotkan, perlu docker cp atau mountLangsung
Kepatuhan lintas OSBekerja di Linux dan Windows containerTerikat struktur dan OS host
Rekomendasi resmi“the preferred mechanism for persisting data”Dipakai bila berkas harus diakses host dan container

Halaman volume menyatakan volume adalah mekanisme yang diutamakan untuk menyimpan data, dan kinerjanya lebih baik karena menulis langsung ke filesystem host, bukan ke lapisan container yang memakai union filesystem. Volume juga bisa diisi otomatis: saat volume kosong di-mount ke direktori yang sudah berisi berkas di image, isi direktori itu disalin ke volume. Bind mount punya risiko berbeda: “Bind mounts have write access to files on the host by default”, jadi batasi dengan readonly bila tidak perlu menulis.

  • Pahami risiko --rm dan docker volume prune. --rm hanya menghapus volume anonim (tanpa nama); volume bernama tetap ada. Sebaliknya docker volume prune menghapus semua volume yang tidak dipakai container mana pun, termasuk milik proyek lain di host yang sama:
bash
docker run --rm -v /anon -v data-app:/named alpine true
docker volume inspect data-app --format '{{.Name}} masih ada'

Langkah 7: Hubungkan Container lewat Jaringan

Langkah ini memperagakan perbedaan yang paling sering mengejutkan: jaringan bawaan tidak menyediakan DNS antar container, sedangkan jaringan buatan yang Anda buat sendiri menyelesaikan nama antar container secara otomatis.

  • Coba me-resolve nama pada jaringan bawaan. Dua container di bridge bawaan hanya dapat saling menghubungi lewat alamat IP, bukan nama:
bash
docker run -d --name srv1 alpine sleep 300
docker run --rm alpine nslookup srv1
text
** server can't find srv1: NXDOMAIN
  • Buat jaringan bridge buatan sendiri lalu sambungkan container. Jaringan ini mengaktifkan DNS bawaan Docker di 127.0.0.11:
bash
docker network create app-net
docker network connect app-net srv1
docker run --rm --network app-net alpine sh -c 'cat /etc/resolv.conf; nslookup srv1'
text
# Generated by Docker Engine.
nameserver 127.0.0.11
search .
options edns0 trust-ad ndots:0
---
Name:	srv1
Address: 172.21.0.2
  • Buktikan container saling memanggil lewat nama. Jalankan web server di jaringan itu, lalu panggil dari container lain dengan wget ke hostname:
bash
docker run -d --name web --network app-net nginx:alpine
docker run --rm --network app-net alpine wget -qO- http://web/ | head -3
text
<!DOCTYPE html>
<html>
<head>
PerintahFungsi
docker network create app-netBuat jaringan bridge baru
docker network connect app-net srv1Sambungkan container yang sudah jalan
docker network disconnect app-net srv1Lepaskan container dari jaringan
docker network inspect app-netLihat detail jaringan dan anggotaanya
docker network lsDaftar semua jaringan

Bedakan dua galat DNS ini, karena penyebabnya berbeda. NXDOMAIN berarti tidak ada catatan nama sama sekali, biasanya karena container berada di jaringan bawaan. SERVFAIL yang saya alami saat pengujian berarti resolver menjangkau Docker tetapi entri-nya tidak tersedia, yang sering terjadi karena container target sudah berhenti. Pada langkah di atas, percobaan pertama gagal dengan SERVFAIL karena httpd tidak ada di image alpine sehingga container web langsung exit dengan kode 127.

Langkah 8: Rangkai Dua Service dengan Compose

Langkah ini memindahkan seluruh hasil langkah sebelumnya ke satu berkas deklaratif yang dapat dijalankan ulang.

  • Tulis satu berkas compose.yaml. Nama service menjadi hostname di jaringan yang sama, dan down -v membersihkan volume serta jaringan milik proyek ini:
yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8084:80"
    volumes:
      - konten-situs:/usr/share/nginx/html
    networks:
      - aplikasi

  api:
    image: halo-app:latihan
    environment:
      PORT: "8080"
    networks:
      - aplikasi

volumes:
  konten-situs:

networks:
  aplikasi:
    driver: bridge
  • Validasi berkas sebelum menjalankan apa pun. Perintah config hanya mem-parsing dan memvalidasi, tidak menyentuh host:
bash
docker compose -p latihan config --quiet
  • Jalankan, periksa, lalu panggil antar service. Service api dapat memanggil web cukup dengan menulis http://web/:
bash
docker compose -p latihan up -d
docker compose -p latihan ps
docker compose -p latihan exec -T api wget -qO- http://web/ | head -3
docker compose -p latihan down -v

Volume konten-situs terisi otomatis dengan index.html dan 50x.html dari image nginx, karena volume kosong di-mount ke direktori yang sudah berisi berkas.

Langkah 9: Bersihkan Sisa Uji

Langkah ini membersihkan container, image, dan cache yang dibuat tutorial ini, tanpa menyentuh proyek lain di host.

  • Lihat berapa ruang yang terpakai. docker system df memisahkan image, container, volume, dan build cache:
bash
docker system df
text
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          9         4         1.663GB   545.8MB (32%)
Containers      6         3         114.7kB   24.58kB (21%)
Local Volumes   8         1         949.5MB   884MB (93%)
Build Cache     25        0         244.7MB   125.1MB
  • Hapus hanya yang milik tutorial ini. Menyebut nama satu per satu lebih aman daripada docker system prune -a, yang juga menyentuh image proyek lain:
bash
docker stop halo-app halo-local srv1 web
docker rm halo-app halo-local srv1 web
docker image rm halo-app:latihan halo-app:build-stage demo-web:latihan
docker volume rm data-app
docker network rm app-net
  • Kosongkan build cache bila perlu. docker builder prune menghapus cache yang tidak dipakai image mana pun. Cache bersifat dapat dibangun ulang, tetapi membersihkannya membuat build berikutnya mengunduh ulang lapisan:
bash
docker builder prune

Pada host yang menjalankan beberapa proyek Compose, docker system prune -a --volumes dan docker volume prune bisa menghapus image serta volume milik proyek lain yang sedang tidak aktif. Gunakan docker system df untuk melihat rinciannya, lalu hapus per nama.

Jebakan yang Saya Temui Saat Menjalankan

GejalaPenyebabSolusi
cannot attach stdin to a TTY-enabled container because stdin is not a terminaldocker exec -it dijalankan di konteks tanpa terminal, misalnya dari pipeline atau sesi non-interaktifTambahkan -it hanya saat memakai shell interaktif; untuk perintah sekali jalan, cukup docker exec
sh: httpd: not found, container langsung exitApplet httpd tidak tersedia di image alpine, sehingga DNS akhirnya memberi SERVFAILGunakan image yang memang menyediakan layanannya, dan periksa docker logs sebelum mengira masalah jaringan
Build kedua tetap ingin mengunduh layer--no-cache hanya mematikan cache build, bukan mengambil image dasar baruGunakan --pull untuk memaksa ambil versi terbaru
Perubahan kecil memicu build ulang yang panjangCOPY . . membatalkan seluruh lapisan setelahnyaSalin berkas spesifik, dan buat .dockerignore
permission denied saat menulis berkas dari containerBind mount mewarisi kepemilikan UID/GID host, sedangkan container sering berjalan sebagai nobodySamakan UID/GID di Dockerfile dengan UID pengguna host, atau tulis ke volume
Data hilang setelah docker rmData ditulis ke lapisan container, bukan volumePasang volume bernama pada direktori data aplikasi

Cheatsheet Perintah Sehari-hari

TujuanPerintah
Lihat container berjalandocker ps
Lihat container berhentidocker ps -a
Lihat log containerdocker logs -f <nama>
Masuk ke shell containerdocker exec -it <nama> sh
Hentikan lalu hapusdocker stop <nama> && docker rm <nama>
Lihat imagedocker image ls
Lapisan sebuah imagedocker image history <image>
Build ulang tanpa cachedocker build --no-cache -t <nama>:<tag> .
Daftar volumedocker volume ls
Daftar jaringandocker network ls
Bersihkan container berhentidocker container prune

Dengan semuanya ini, Anda sudah bisa menjalankan aplikasi Anda sendiri dalam container, menyimpan datanya di luar container, dan menyatakannya lewat jaringan internal. Langkah berikutnya biasanya adalah menulis image multi-stage untuk aplikasi nyata, lalu mengotomatiskan build dan deploy-nya di CI.