Image katalog-web kini hidup di Docker Hub dan bisa ditarik dari mana saja — setidaknya dari mana saja yang tersambung internet dan bersedia berbagi kuota dengan publik. Namun Docker Hub bukan satu-satunya alamat di dunia: ada tim yang bekerja di jaringan terisolasi tanpa akses internet, ada perusahaan yang regulasinya melarang image internal keluar pusat data, dan ada kantor yang muak menunggu pull ratusan megabyte melewati jalur publik. Untuk semua skenario itu Docker menyediakan jawaban yang mengejutkan ringkas: registry sendiri berjalan sebagai — apa lagi — sebuah container. Chapter ini menjalankan registry privat dengan image resmi registry:2, menguji siklus tag-push-pull ke localhost:5000, lalu menaklukkan dua urusan operasionalnya yang paling sering menggigit: TLS dan autentikasi.

Mengapa Menjalankan Registry Sendiri #

Empat motivasi berikut paling sering menjadi alasan tim memutuskan bahwa image tidak boleh lagi menumpang registry publik:

MotivasiCerita di lapangan
Jaringan terisolasi (air-gapped)lingkungan tanpa internet tetap bisa mendistribusikan image antar mesin
Latensi rendahpull berukuran gigabyte berjalan melalui LAN, bukan jalur publik
Kepatuhan (compliance)image yang membawa data terbatas tidak pernah meninggalkan pusat data
Kendali penuhkuota, retensi, dan kebijakan akses ditentukan sendiri

Registry self-host juga punya sisi yang perlu jujur diakui sejak awal: tanpa TLS dan autentikasi ia terbuka lebar, tanpa perawatan volumenya bisa penuh oleh blob lama, dan tanpa infrastruktur pendukung ia tidak membawa replikasi maupun pemindaian kerentanan bawaan. Tim besar biasanya berakhir di registry terkelola — Amazon ECR, Artifact Registry, Quay — di tengah jalan; registry:2 tetap unggul untuk belajar, lab, dan skenario terisolasi.

Menjalankan registry:2 #

Satu perintah docker run sudah cukup untuk menghidupkan registry penuh fitur:

bash
$ docker run -d -p 5000:5000 --restart always --name registry -v registry-data:/var/lib/registry registry:2

Perintah ini menarik image registry:2 dari Docker Hub lalu menjalankannya. Kita verifikasi kehidupannya lewat daftar container:

bash
$ docker ps

Output yang Dihasilkan:

text
CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS         PORTS                                       NAMES
dfb41e0f3f6a   registry:2   "/entrypoint.sh /etc…"   8 seconds ago   Up 7 seconds   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   registry

Mari kita telaah enam bagian perintah pembangunnya:

  1. -d — registry berjalan di background sebagai layanan jangka panjang, bukan sesi interaktif.
  2. -p 5000:5000 — port 5000 host diteruskan ke port 5000 container; port standar tempat Registry HTTP API V2 mendengarkan, API yang endpoint /v2/-nya kita kenal dari Chapter Pengantar Container Registry.
  3. --restart always — kebijakan restart memastikan registry bangkit sendiri setelah daemon restart atau host direboot; tanpa ini, satu reboot server melumpuhkan seluruh tim.
  4. --name registry — nama tetap supaya mudah dikelola lewat docker logs registry atau docker exec registry.
  5. -v registry-data:/var/lib/registry — named volume menyimpan seluruh blob dan manifest di luar filesystem container; container boleh dihapus dan dibuat ulang, isi registry tetap aman — pola persistensi dari Section Penyimpanan Data.
  6. registry:2 — image resmi open source dari proyek Distribution; tag 2 menandai garis rilis mayor yang aktif dipelihara.

Sanity check terakhir: endpoint /v2/ adalah akar API yang wajib dijawab setiap registry sehat:

bash
$ curl http://localhost:5000/v2/

Output yang Dihasilkan:

text
{}

Jawaban JSON kosong berarti registry hidup, berbicara API V2, dan siap menerima push.

Tag, Push, dan Pull dari Registry Lokal #

Mekanisme distribusinya persis sama dengan Docker Hub — yang berubah hanya alamat. Kita pasang prefiks hostname pada nama image, lalu dorong:

bash
$ docker tag katalog-web:2.0 localhost:5000/katalog-web:2.0
$ docker push localhost:5000/katalog-web:2.0

Output yang Dihasilkan:

text
The push refers to repository [localhost:5000/katalog-web]
7a3e9c1f5d24: Pushed
c48e2b6d90af: Pushed
5f1d8a3c7e29: Pushed
9b2e6fa8d41c: Pushed
e6c417f2a89b: Pushed
2.0: digest: sha256:e3a47392fbb44a7fec80ab4002e269f8c6e71c769411fc9e102d59529137e8c6 size: 1570

Perhatikan satu detail yang sering membuat mata terbelalak: baris digest identik dengan hasil push ke Docker Hub pada Chapter Push dan Pull Image. Digest dihitung dari isi manifest dan layer, bukan dari lokasi penyimpanan — image yang sama selalu berdigest sama di registry mana pun. Inilah bukti content-addressable lintas registry: dua server yang bahkan tidak saling mengenal menyimpan objek yang dapat dibuktikan identik. Kita uji arah sebaliknya:

bash
$ docker pull localhost:5000/katalog-web:2.0

Output yang Dihasilkan:

text
2.0: Pulling from katalog-web
Digest: sha256:e3a47392fbb44a7fec80ab4002e269f8c6e71c769411fc9e102d59529137e8c6
Status: Image is up to date for localhost:5000/katalog-web:2.0

Status Image is up to date muncul karena push barusan turut mencatat referensi localhost:5000/katalog-web:2.0 pada penyimpanan lokal — pull dari registry lokal berhasil, hanya saja memang tidak ada yang perlu diunduh.

Saat Daemon Menolak HTTP Biasa #

Cerita berjalan mulus selama alamatnya localhost. Masalah muncul begitu registry diakses dari mesin lain — Budi membiarkan registrynya hidup di workstation 192.168.1.20, lalu Siti menarik image lewat LAN:

bash
$ docker pull 192.168.1.20:5000/katalog-web:2.0

Output yang Dihasilkan:

text
Error response from daemon: Get "https://192.168.1.20:5000/v2/": http: server gave HTTP response to HTTPS client

Pesan ini jujur menceritakan kejadiannya: daemon Siti mencoba bicara HTTPS — protokol bawaan untuk registry selain localhost — sedangkan registry kita baru berbicara HTTP polos. Alamat localhost dan 127.0.0.1 memang dikecualikan dari kebijakan ini secara bawaan; itulah sebabnya latihan kita tadi lancar. Alamat lain tidak mendapat pengecualian apa pun.

Solusi cepat untuk laboratorium adalah mendaftarkan registry ke daftar insecure-registries pada berkas daemon.json milik mesin klien, lalu memulai ulang daemon:

json
{
  "insecure-registries": ["192.168.1.20:5000"]
}

Pada Linux kita restart layanan dengan sudo systemctl restart docker; pada Docker Desktop, berkas yang sama diedit melalui Settings → Docker Engine. Setelah daemon bangkit, docker info menampilkan bukti konfigurasi terbaca:

text
...
 Insecure Registries:
  127.0.0.0/8
  192.168.1.20:5000
...

Dan pull Siti kini berjalan lancar — dengan hasil yang layak direnungkan:

text
2.0: Pulling from katalog-web
7a3e9c1f5d24: Already exists
c48e2b6d90af: Already exists
5f1d8a3c7e29: Already exists
9b2e6fa8d41c: Already exists
e6c417f2a89b: Already exists
Digest: sha256:e3a47392fbb44a7fec80ab4002e269f8c6e71c769411fc9e102d59529137e8c6
Status: Downloaded newer image for 192.168.1.20:5000/katalog-web:2.0

Lima baris Already exists muncul karena laptop Siti sudah menyimpan seluruh layer itu — bukan dari registry ini, melainkan dari tarikan Docker Hub pada Chapter Push dan Pull Image. Digest yang sama dikenali lintas registry, sehingga nol byte pun tidak diunduh ulang; baris Status: Downloaded newer image hanya mencatat bahwa nama referensi 192.168.1.20:5000/katalog-web:2.0 baru pertama kali terpasang di mesin itu.

Jalan yang Benar: TLS dengan Sertifikat #

Solusi insecure-registries meneguk kenyamanan dengan menuju risiko; jalan yang benar untuk produksi adalah TLS. Prosesnya ringkas dan berlangkah:

  1. Siapkan sertifikat beserta kuncinya dari certificate authority — CA internal perusahaan atau CA publik — untuk nama host registry, misalnya registry.example.com.
  2. Mount kedua berkas tersebut ke container registry, lalu arahkan dua environment variable padanya.
  3. Distribusikan sertifikat CA ke setiap mesin klien: salin ke /etc/docker/certs.d/registry.example.com:5000/ca.crt, atau tambahkan CA ke trust store sistem operasi.
  4. Uji dengan docker login registry.example.com:5000 — bila berhasil tanpa konfigurasi insecure apa pun, TLS bekerja.

Bentuk lengkap langkah keduanya:

bash
$ docker rm -f registry
$ docker run -d -p 5000:5000 --restart always --name registry \
    -v registry-data:/var/lib/registry \
    -v /srv/registry/certs:/certs:ro \
    -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.example.com.crt \
    -e REGISTRY_HTTP_TLS_KEY=/certs/registry.example.com.key \
    registry:2

Variabel REGISTRY_HTTP_TLS_CERTIFICATE dan REGISTRY_HTTP_TLS_KEY memerintahkan registry menyajikan TLS pada port yang sama, sementara volume lama registry-data tetap terpasang sehingga blob yang sudah tersimpan tidak kehilangan rumah. Sejak saat itu docker login, push, dan pull ke registry.example.com:5000 bekerja normal — daemon memverifikasi sertifikat, lalu lintas terenkripsi, dan tidak ada satu pun entri insecure yang dibutuhkan.

Autentikasi dengan htpasswd #

Registry tanpa autentikasi terbuka bagi siapa saja yang bisa menjangkaunya — dan registry yang baru saja kita bekali TLS kini siap menerima kredensial dengan aman. Mode autentikasi paling sederhana yang didukung registry:2 adalah berkas htpasswd. Kita menghasilkannya tanpa memasang tool apa pun di host, cukup meminjam image registry yang entripoint-nya kita ganti sementara:

bash
$ mkdir -p /srv/registry/auth
$ docker run --rm --entrypoint htpasswd registry:2 -Bbn budi rahasia123 > /srv/registry/auth/htpasswd

Flag -B meminta hash bcrypt, -b membaca password dari argumen, dan -n menulis hasilnya ke stdout yang kita alihkan ke berkas. Isinya satu baris:

bash
$ cat /srv/registry/auth/htpasswd

Output yang Dihasilkan:

text
budi:$2y$05$4Wn6KzEcHAaLdOX38RDPaVnwzp66614wdl/QzcLD3G63QB0BzYtXb

Lalu registry dibangkitkan ulang dengan tiga variable autentikasi:

bash
$ docker rm -f registry
$ docker run -d -p 5000:5000 --restart always --name registry \
    -v registry-data:/var/lib/registry \
    -v /srv/registry/auth:/auth:ro \
    -e REGISTRY_AUTH=htpasswd \
    -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Privat" \
    -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
    registry:2

Sejak saat itu setiap push dan pull harus didahului docker login registry.example.com:5000 -u budi — pola login per registry yang persis kita latih pada Chapter Login dan Authentication, karena registry mana pun berbicara protokol yang sama. Outputnya kini bersih berkat credential helper yang sudah aktif sejak chapter itu:

bash
$ docker login registry.example.com:5000 -u budi

Output yang Dihasilkan:

text
Password:
Login Succeeded

Kredensial hanya layak dikirim melalui TLS — itulah urutan bab ini: sertifikat lebih dulu, baru htpasswd. Menggabungkan htpasswd dengan HTTP polos berarti mengirim password terenkripsi-hash tetapi menampilkannya di jalur yang bisa disadap.

Memilih Bentuk Deployment #

Seluruh perjalanan tadi bisa diringkas menjadi tabel keputusan:

SkenarioKonfigurasiCatatan
Lab dan jaringan terpercayaHTTP + insecure-registries di klienpaling cepat dirakit; lalu lintas polos
ProduksiTLS + htpasswd + named volumekredensial terenkripsi; CA didistribusikan ke klien
Skala besarregistry terkelola (ECR, Artifact Registry, Quay)replikasi, pemindaian, dan kuota bawaan

Referensi #

Registry privat menjawab kebutuhan menyimpan dan membagikan image milik kita sendiri. Namun satu pekerjaan distribusi masih tersisa: menarik image publik seperti nginx dan node berulang kali dari Docker Hub — lengkap dengan jarak jaringan dan rate limit-nya. Untuk itu Docker punya konsep registry mirror: cache perantara yang menyimpan image publik di dekat kita, topik chapter berikutnya.