Janji penutup chapter sebelumnya kini jatuh tempo: membereskan tumpukan image di penyimpanan lokal. Sejak Section Dasar Docker CLI kita menarik image demi image — nginx dalam tiga wujud, alpine, hello-world — dan setiap pull meninggalkan ratusan megabita yang tinggal menetap di disk. Penghapusan image di Docker bukan sekadar soal satu perintah docker rmi: output-nya memuat dua jenis baris yang maknanya berbeda (Untagged dan Deleted), daemon bisa menolak dengan alasan yang wajar, dan flag paksa punya batas yang perlu dipahami sebelum dipakai. Chapter ini membedah semuanya, lalu menutup dengan pembersihan menyeluruh terhadap image pertama yang pernah kita jalankan: hello-world.

Menghapus Image dengan docker rmi #

Perintah penghapusan image adalah docker rmi, anggota tua yang kini punya bentuk modern docker image rm — keduanya identik, sama seperti hubungan docker images dengan docker image ls yang kita bahas pada Chapter Melihat Image Lokal. Argumennya adalah referensi image: tag atau IMAGE ID. Supaya demo-nya berdampak nyata, kita perlukan satu image baru yang memang diperuntukkan menjadi korban — varian alpine nginx yang sudah kita kenal pada Chapter Menarik Image:

bash
$ docker pull nginx:1.29-alpine

Output yang Dihasilkan:

text
1.29-alpine: Pulling from library/nginx
d4c8b2f7a9e1: Pull complete
3f6b1c9e2a7d: Pull complete
...
Digest: sha256:6d9f2c8a4e7b1d5f3c9a2e8b7d4f1c6a9e3b5d7f2c8a4e1b6d9f3c7a5e2b8d1f
Status: Downloaded newer image for nginx:1.29-alpine
docker.io/library/nginx:1.29-alpine

Tiga baris layer di tengah dipotong — anatomi lengkap output pull sudah kita bedah pada Chapter Menarik Image. Image 47.9MB itu kini tersimpan, dan satu-satunya alasan keberadaannya adalah agar bisa dihapus. Mari kita jalankan:

bash
$ docker rmi nginx:1.29-alpine

Output yang Dihasilkan:

text
Untagged: nginx:1.29-alpine
Untagged: nginx@sha256:6d9f2c8a4e7b1d5f3c9a2e8b7d4f1c6a9e3b5d7f2c8a4e1b6d9f3c7a5e2b8d1f
Deleted: sha256:5b1e8c3a9f7d...
Deleted: sha256:c9f3a7e5b1d2...
...

Nilai lengkap setiap baris Deleted: adalah 64 karakter heksadesimal yang dipotong agar ringkas, dan tiga baris lainnya terpotong oleh .... Mari kita telaah jenis-jenis barisnya:

  1. Untagged: nginx:1.29-alpine — label tag dilepas dari image; ini persis kebalikan dari apa yang terjadi ketika tag ditempelkan pada Chapter Memahami Image Tag.
  2. Untagged: nginx@sha256:... — referensi digest ikut dilepas; baris ini muncul karena image pernah ditarik dari registry, sehingga daemon menyimpan RepoDigests — materi Chapter Image Digest. Image yang kita bangun sendiri di lokal tidak punya baris ini.
  3. Baris-baris Deleted: — layer-layer image dibuang dari penyimpanan lokal; ada lima baris selaras dengan lima layer nginx:1.29-alpine, diurutkan dari layer teratas. Baris Deleted: hanya muncul untuk layer yang tidak dipakai image lain — layer yang dibagikan, sebagaimana kita bedah pada Chapter Konsep Image Layer, dibiarkan tetap tinggal.
  4. Urutan makna — nama dilepas dulu, isi dibuang kemudian; selama masih ada nama lain yang menempel pada image yang sama, Docker berhenti di langkah pertama, dan keajaiban kecil inilah yang kita saksikan berikutnya.

Ketika Docker Menolak: Image Masih Dipakai #

Docker tidak akan pernah menghapus layer yang sedang dipakai container — menghapusnya sama saja dengan menarik karpet dari bawah proses yang sedang berjalan. Mesin latihan kita punya satu container web nginx yang menyala sejak Section Dasar Docker CLI, jadi mari kita coba menghapus image asalnya:

bash
$ docker rmi nginx:latest

Output yang Dihasilkan:

text
Error response from daemon: conflict: unable to delete 613f27b0341a (cannot be forced) - image is being used by running container f7c2a91b3e55

Mari kita telaah pesan penolakan tersebut:

  1. conflict — jenis galat khas bentrok referensi; daemon bukan gagal teknis, melainkan menolak dengan sengaja.
  2. unable to delete 613f27b0341a — image yang dimaksud dilaporkan lewat IMAGE ID pendek, sama dengan yang tampil pada docker images.
  3. image is being used by running container f7c2a91b3e55 — alasan penolakan yang jujur: container web yang berjalan saat ini bertumpu pada image ini; ID pendek f7c2a91b3e55 adalah container web ciptaan Chapter Menjalankan Container Pertama.
  4. (cannot be forced) — klausul penting: bahkan flag paksa tidak bisa melampaui penolakan ini, karena layer sedang dipakai proses hidup.

Flag -f tetap layak dicoba supaya penolakannya terlihat mata:

bash
$ docker rmi -f nginx:latest

Output yang Dihasilkan:

text
Error response from daemon: conflict: unable to delete 613f27b0341a (cannot be forced) - image is being used by running container f7c2a91b3e55

Hasilnya sama persis. Untuk image yang dipakai container hidup, satu-satunya jalan adalah membereskan container lebih dulu — stop lalu hapus, keduanya materi Chapter Menghentikan dan Menjalankan Container dan Menghapus Container. Flag -f sesungguhnya bekerja pada dua kasus lain: image yang dipakai container berhenti (image terhapus, tetapi container bangkainya tidak akan bisa distart lagi karena cetakbirunya sudah tidak ada), dan image yang dirujuk banyak tag saat dihapus berdasarkan IMAGE ID (semua tag dilepas sekaligus). Kasus pertama kita saksikan di demo penutup chapter ini; sampai saat itu tiba, kita biarkan web tetap menyala dan menyimpan imagenya.

Untagged Saja: Menghapus Salah Satu Tag #

Bagian paling menarik dari perilaku docker rmi muncul ketika satu image ditunjuk banyak tag. Mesin kita menyimpan tepat kasus ini: tag nginx:latest dan nginx:1.29.4 sama-sama menunjuk image 613f27b0341a, bukti yang kita ukir pada Chapter Memahami Image Tag. Mari kita hapus salah satunya:

bash
$ docker rmi nginx:1.29.4

Output yang Dihasilkan:

text
Untagged: nginx:1.29.4

Satu baris, tanpa Deleted: apa pun. Mari kita telaah artinya:

  1. Hanya nama yang hilang — docker rmi dengan argumen tag memang hanya melepas label yang disebut; tag latest masih menempel, jadi image dan seluruh layernya tetap utuh di penyimpanan lokal.
  2. Tidak ada referensi digest yang dilepas — referensi nginx@sha256:... tetap dimiliki image karena masih hidup dan masih dikenal registry; baris Untagged kedua baru muncul ketika image benar-benar dihapus.
  3. Urutan pembersihan yang benar — untuk menghapus image multi-tag secara utuh, hapus tag satu per satu; baris Deleted: baru muncul saat tag terakhir dilepas. Alternatif yang lebih cepat: docker rmi -f 613f27b0341a melepas semua tag sekaligus — dengan konsekuensi bahwa penolakan “sedang dipakai container” tidak lagi bisa dielakkan per-tag.

Perilaku “hapus satu tag = Untagged saja” inilah yang menjelaskan fenomena image dangling pada Chapter Melihat Image Lokal: build ulang dengan tag yang sama secara diam-diam melepas tag dari image lama, meninggalkan image tanpa nama yang hanya bisa dirujuk lewat ID.

Menghapus Berdasarkan IMAGE ID #

Image menganggur tidak punya tag untuk disebut, sehingga IMAGE ID menjadi satu-satunya alamatnya. Mesin kita masih menyimpan satu sisa sejarah milik Budi: image 105c9de1d398 berukuran 212MB yang kehilangan namanya setelah build ulang myapp:dev — baris <none> yang kita jumpai pada Chapter Melihat Image Lokal. Mari kita hapus dengan ID-nya:

bash
$ docker rmi 105c9de1d398

Output yang Dihasilkan:

text
Deleted: sha256:105c9de1d398...
Deleted: sha256:4b7d9e1c3a5f...
...

Baris Deleted: yang tampil lebih banyak dari dua baris di atas dipotong — image sebesar itu tersusun dari banyak layer, dan seluruhnya tidak dipakai image lain sehingga semuanya dibuang. Mari kita telaah:

  1. Tanpa baris Untagged — image memang tidak punya nama yang bisa dilepas; penghapusan langsung bekerja pada isi.
  2. 212MB kembali ke disk — ruang yang selama ini dikuasai image tanpa nama dan tanpa manfaat akhirnya dibebaskan; inilah alasan image menganggur selalu menjadi target pembersihan pertama.
  3. Cara massalnya — bila image menganggur berjumlah banyak, docker rmi satu per satu melelahkan; docker image prune membersihkan semuanya sekaligus:
bash
$ docker image prune

Output yang Dihasilkan:

text
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

Angka 0B pada mesin kita masuk akal: baris <none> tadi baru saja kita hapus manual lewat IMAGE ID, sehingga tidak ada yang tersisa untuk diprune. Output tersebut tetap memperlihatkan dua perilaku penting: perintah meminta konfirmasi karena sifatnya massal, dan jangkauannya terbatas pada image menganggur saja — image yang masih bertag dibiarkan apa adanya. Pembahasan lengkap keluarga perintah prune, termasuk varian yang jauh lebih agresif, menunggu di Chapter Membersihkan Resource Image.

Peta Skenario Penghapusan #

Empat demo di atas plus satu demo penutup di bawah merangkum seluruh wajah docker rmi. Tabel berikut menjadi peta cepat ketika kita menghadapi penghapusan image sehari-hari:

SkenarioPerintahHasil
image dengan satu tag, tanpa pemakaidocker rmi nginx:1.29-alpineUntagged + Deleted seluruh layer
image dengan dua tag, hapus satu tagdocker rmi nginx:1.29.4Untagged saja — image tetap utuh
image dipakai container hidupdocker rmi nginx:latestditolak; stop dan hapus container lebih dulu
image dipakai container berhentidocker rmi hello-worldditolak; hapus container dulu, atau paksa -f
image menganggur tanpa namadocker rmi 105c9de1d398Deleted — IMAGE ID satu-satunya alamat
membersihkan semua image menganggurdocker image prunemassal dengan konfirmasi

Membereskan hello-world dari Awal sampai Akhir #

Sudah saatnya kita melunasi utang rumah tangga paling lama di mesin latihan ini. Image hello-world berukuran 13.3kB menemani kita sejak Chapter Menjalankan Container Pertama, dan container bangkainya — si quirky_einstein — masih parkir dengan status berhenti sejak chapter itu. Kita hapus image-nya terlebih dahulu supaya penolakannya terlihat:

bash
$ docker rmi hello-world

Output yang Dihasilkan:

text
Error response from daemon: conflict: unable to delete e6590340b2f4 (must be forced) - image is being used by stopped container d4e5f6a7b8c9

Perhatikan perbedaan klausulnya dengan penolakan sebelumnya: kini (must be forced) — image dipakai container yang sudah berhenti, sehingga flag -f sungguh-sungguh akan menghapus image dan meninggalkan quirky_einstein sebagai bangkai yang tidak bisa dihidupkan lagi. Kita memilih jalan bersih: container-nya dulu, image-nya kemudian — persis urutan yang diajarkan Chapter Menghapus Container:

bash
$ docker rm quirky_einstein

Output yang Dihasilkan:

text
d4e5f6a7b8c9

Baris ID pendek d4e5f6a7b8c9 adalah tanda balasan standar docker rm — container quirky_einstein dan writable layer-nya telah terbuang, sebagaimana model layer pada Chapter Konsep Image Layer menjanjikan. Kini tidak ada lagi yang memakai hello-world:

bash
$ docker rmi hello-world

Output yang Dihasilkan:

text
Untagged: hello-world:latest
Untagged: hello-world@sha256:6c15599d177bd6b1e977d6aa4e532bdf00dc5c22513503c9cc85c027b3ccab9c
Deleted: sha256:ef151d431693...

Mari kita telaah penutup kecil ini:

  1. Dua baris Untagged — tag latest dan referensi digest hello-world@sha256:6c15... (digest yang sama yang tercetak pada pull pertama kita) dilepas berurutan.
  2. Satu baris Deleted — hello-world hanya tersusun dari satu layer ef151d431693, jadi hanya satu baris; perbandingan yang pas dengan lima baris Deleted milik nginx:1.29-alpine di awal chapter.
  3. Mesin kini lebih ramping — nginx:1.29-alpine, image menganggur milik Budi, dan hello-world telah tiada; yang tersisa hanyalah image yang benar-benar dipakai.

Penghapusan adalah setengah dari seni merawat penyimpanan image; setengah satunya adalah membaca image yang tersisa dengan teliti. Tag, digest, IMAGE ID, dan SIZE hanyalah kolom-kolom tabel — di baliknya tersimpan metadata penuh yang menjelaskan bagaimana sebuah image dibangun dan apa yang akan dilakukannya saat dijalankan. docker image inspect dan docker history membuka isi tersebut sampai ke instruksi build paling dasar — materi Chapter Image Inspect dan History.

Referensi #