Private Registry

Private Registry

Bitnesia Sep 12, 2026 10 EN

Docker Hub sangat praktis untuk image publik, tapi banyak organisasi tidak bisa menaruh image aplikasi internal begitu saja di registry pihak ketiga, entah karena kebijakan keamanan, kepatuhan regulasi, batas jumlah repository privat di paket gratis, atau sekadar kebutuhan latensi push/pull yang lebih rendah karena registry berada di jaringan yang sama dengan server production. Solusinya adalah menjalankan private registry sendiri, yaitu server registry yang dikelola dan dihosting mandiri, biasanya memakai Distribution, proyek open source di bawah CNCF yang menjadi implementasi registry resmi Docker. Bab ini membahas cara menjalankan registry sendiri, mengatur konfigurasinya lewat config.yml, mengamankannya dengan TLS dan autentikasi, sampai berinteraksi langsung dengan API registry untuk kebutuhan otomasi.

20.1 Self-Hosted Registry

Distribution dibungkus dalam image resmi bernama registry, sehingga menjalankan registry sendiri secara mendasar sama mudahnya dengan menjalankan container lain. Bagian ini membahas cara menjalankannya beserta pertimbangan penyimpanan data.

20.1.1 Menjalankan Registry dengan Docker

Jalankan container registry memakai image resmi registry dengan tag versi mayor 3, versi Distribution yang saat ini aktif dikembangkan dan direkomendasikan untuk deployment baru.

docker run -d -p 5000:5000 --restart=always --name registry registry:3

Perintah di atas menjalankan registry di background (-d), memetakan port 5000 di host ke port 5000 di container tempat registry mendengarkan permintaan HTTP, dan memberi restart policy always supaya registry otomatis menyala lagi kalau Docker daemon di-restart. Setelah container berjalan, registry langsung bisa dicoba dengan menandai ulang (tag) image lokal memakai alamat registry ini, lalu push seperti biasa.

docker tag my-app localhost:5000/my-app:1.0
docker push localhost:5000/my-app:1.0
docker pull localhost:5000/my-app:1.0

Perhatikan bagian localhost:5000/ di depan nama image. Sama seperti format nama image yang dibahas pada bab tentang Docker Hub, bagian ini adalah HOST[:PORT] yang memberi tahu Docker CLI ke mana permintaan push/pull harus diarahkan, alih-alih ke docker.io secara default.

Kalau alamat registry bukan localhost, misalnya diakses lewat IP atau domain internal, percobaan push biasanya langsung gagal dengan pesan http: server gave HTTP response to HTTPS client. Pesan ini muncul karena Docker CLI selalu mengasumsikan registry berjalan di atas HTTPS kecuali alamatnya localhost. Registry semacam ini butuh sertifikat TLS supaya bisa diakses secara aman dari mesin lain, atau kalau memang cuma untuk eksperimen di jaringan tertutup, alamatnya bisa didaftarkan sebagai insecure registry di konfigurasi Docker Engine.

20.1.2 Persistensi Data Registry

Secara default, registry menyimpan seluruh layer image yang di-push ke direktori /var/lib/registry di dalam container. Tanpa volume, data ini ikut hilang begitu container dihapus, sebuah kejadian yang cukup sering menjebak Sysadmin/DevOps Engineer yang baru pertama kali mencoba registry sendiri lalu kaget saat container-nya di-docker rm untuk keperluan upgrade dan seluruh image yang pernah di-push ikut lenyap. Pasang bind mount atau named volume supaya data registry tetap ada terlepas dari siklus hidup container-nya.

docker run -d -p 5000:5000 --restart=always --name registry \
  -v /mnt/registry-data:/var/lib/registry \
  registry:3

Untuk kebutuhan production dengan volume data besar atau yang butuh replikasi lintas server, Distribution juga mendukung storage driver selain filesystem lokal, misalnya Amazon S3, Google Cloud Storage, atau Azure Blob Storage. Pemilihan storage driver ini diatur lewat bagian storage di dalam config.yml.

20.2 Konfigurasi Registry

Menjalankan registry lewat opsi docker run sederhana cukup untuk eksperimen cepat, tapi pengaturan yang lebih lengkap seperti storage driver, TLS, dan autentikasi perlu ditulis dalam file config.yml yang dimuat oleh registry saat startup.

20.2.1 Struktur config.yml

Registry membaca konfigurasinya dari sebuah file YAML yang secara default dicari di path /etc/distribution/config.yml di dalam container. Struktur minimalnya terdiri dari beberapa bagian utama: version, storage, http, dan opsional auth serta health.

version: 0.1

log:
  fields:
    service: registry

storage:
  filesystem:
    rootdirectory: /var/lib/registry

http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]

health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3

Simpan konfigurasi ini di file lokal, misalnya config.yml, lalu mount ke path yang dibaca registry saat container dijalankan.

docker run -d -p 5000:5000 --restart=always --name registry \
  -v /mnt/registry-data:/var/lib/registry \
  -v "$(pwd)"/config.yml:/etc/distribution/config.yml \
  registry:3

Bagian health.storagedriver penting diperhatikan Sysadmin/DevOps Engineer yang mengoperasikan registry di production, karena registry otomatis menandai dirinya unhealthy lewat endpoint /debug/health kalau storage backend bermasalah, sehingga bisa dideteksi lebih awal oleh sistem monitoring sebelum push/pull ke registry mulai gagal masif.

20.2.2 Filesystem vs Object Storage

Driver filesystem memakai disk lokal dan cocok untuk development atau deployment skala kecil dengan satu instance registry saja. Untuk kebutuhan production yang lebih besar, terutama saat registry perlu berjalan di lebih dari satu instance untuk high availability, storage berbasis object storage seperti Amazon S3 lebih tepat karena semua instance registry bisa berbagi backend data yang sama tanpa bergantung pada disk lokal salah satu server.

storage:
  s3:
    accesskey: AKIAEXAMPLE
    secretkey: secretkeyexample
    region: ap-southeast-1
    bucket: registry-images-internal
    encrypt: true
    secure: true
    rootdirectory: /registry

Kredensial accesskey dan secretkey di atas sebaiknya tidak ditulis langsung di file config.yml yang ikut ter-commit ke Git. Registry mendukung penggantian nilai konfigurasi lewat environment variable dengan pola REGISTRY_STORAGE_S3_ACCESSKEY dan REGISTRY_STORAGE_S3_SECRETKEY, sehingga kredensial bisa disuntikkan saat runtime lewat secrets manager platform orchestration yang dipakai, alih-alih ditulis permanen di file konfigurasi.

20.2.3 Mengaktifkan TLS

Docker CLI menolak berkomunikasi dengan registry lewat HTTP biasa kecuali alamatnya localhost atau sudah didaftarkan eksplisit sebagai insecure registry. Untuk registry yang diakses dari mesin lain lewat jaringan, TLS wajib diaktifkan. Siapkan sertifikat TLS (dari certificate authority internal atau layanan seperti Let's Encrypt), lalu arahkan registry untuk memakainya lewat config.yml.

http:
  addr: :443
  tls:
    certificate: /certs/domain.crt
    key: /certs/domain.key
docker run -d -p 443:443 --restart=always --name registry \
  -v /mnt/registry-data:/var/lib/registry \
  -v "$(pwd)"/config.yml:/etc/distribution/config.yml \
  -v "$(pwd)"/certs:/certs \
  registry:3

Setelah TLS aktif, alamat registry yang dipakai untuk push, pull, maupun login pun berubah memakai domain yang tercantum di sertifikat, misalnya registry.internal.perusahaan.com, bukan lagi localhost:5000.

20.3 Keamanan dan Autentikasi

Registry tanpa autentikasi berarti siapa pun yang bisa menjangkau alamatnya di jaringan otomatis bisa push maupun pull image, sebuah kondisi berisiko tinggi kalau registry menyimpan image aplikasi internal yang mengandung source code proprietary. Bagian ini membahas opsi insecure registry untuk kebutuhan testing, lalu autentikasi dasar dengan htpasswd untuk kebutuhan yang lebih serius.

20.3.1 Insecure Registry untuk Testing

Saat masih bereksperimen di jaringan lokal tanpa sertifikat TLS resmi, Docker Engine bisa dikonfigurasi menerima registry HTTP biasa lewat opsi insecure-registries di file daemon.json, biasanya berlokasi di /etc/docker/daemon.json pada Linux.

{
  "insecure-registries": ["registry.internal.lab:5000"]
}

Restart Docker daemon setelah mengubah daemon.json agar konfigurasi baru terbaca, dan ulangi langkah yang sama di setiap mesin yang perlu mengakses registry tersebut.

sudo systemctl restart docker

Opsi ini membuat Docker mengabaikan validasi sertifikat sekaligus mengizinkan koneksi HTTP polos ke registry yang didaftarkan, sehingga trafik push/pull termasuk kredensial login bisa disadap siapa pun yang berada di jalur jaringan yang sama (man-in-the-middle). Attacker yang berhasil menyadap trafik semacam ini bisa mencuri kredensial registry atau menyisipkan image berbahaya di tengah jalan. Karena itu, insecure-registries hanya layak dipakai di jaringan lab tertutup atau environment testing yang benar-benar terisolasi, bukan untuk registry yang menyimpan image production.

20.3.2 Basic Auth dengan htpasswd

Cara paling sederhana mengamankan akses registry adalah basic authentication memakai file htpasswd, format autentikasi warisan Apache HTTP Server yang menyimpan pasangan username dan password (di-hash) dalam satu file teks. Registry hanya mendukung hash bcrypt untuk metode ini, jadi file htpasswd harus dibuat dengan opsi bcrypt secara eksplisit.

mkdir -p auth
docker run --rm --entrypoint htpasswd httpd:2.4 -Bbn admin passwordkuat123 > auth/htpasswd

Perintah di atas memanfaatkan image httpd:2.4 hanya untuk menjalankan utilitas htpasswd bawaannya, tanpa benar-benar menjalankan web server Apache. Flag -B memaksa hash bcrypt, -b membaca password langsung dari argumen command line, dan -n mencetak hasilnya ke stdout alih-alih menulis ke file secara langsung. Tambahkan pengguna lain dengan mengarahkan output ke file yang sama memakai >> supaya baris sebelumnya tidak tertimpa.

docker run --rm --entrypoint htpasswd httpd:2.4 -Bbn deploy passworddeploy456 >> auth/htpasswd

Tambahkan bagian auth di config.yml yang menunjuk ke file htpasswd tersebut.

auth:
  htpasswd:
    realm: basic-realm
    path: /auth/htpasswd
docker run -d -p 443:443 --restart=always --name registry \
  -v /mnt/registry-data:/var/lib/registry \
  -v "$(pwd)"/config.yml:/etc/distribution/config.yml \
  -v "$(pwd)"/certs:/certs \
  -v "$(pwd)"/auth:/auth \
  registry:3

Basic auth mengirim kredensial dalam bentuk Base64, yang secara teknis cuma penyandian, bukan enkripsi, sehingga sangat mudah dibalikkan siapa pun yang menyadap trafiknya. Basic auth wajib dijalankan di atas koneksi TLS supaya kredensial tetap terenkripsi selama perjalanan di jaringan; jangan pernah mengaktifkan auth.htpasswd tanpa http.tls yang juga aktif.

20.3.3 Login dan Kontrol Akses

Setelah autentikasi aktif, Developer maupun Sysadmin/DevOps Engineer yang ingin push atau pull image dari registry ini perlu login terlebih dulu, persis seperti login ke Docker Hub, hanya saja menyebutkan alamat registry secara eksplisit.

docker login registry.internal.perusahaan.com

File htpasswd hanya menyediakan satu tingkat akses: pengguna yang berhasil login bisa push dan pull ke repository mana pun di registry tersebut, tanpa pembedaan hak akses per repository atau per tim. Kalau kebutuhannya sudah sampai pada kontrol akses granular, misalnya tim tertentu hanya boleh pull sementara tim lain boleh push, Distribution menyediakan mekanisme token-based authentication yang mendelegasikan proses otorisasi ke server autentikasi terpisah di luar registry. Setup token-based authentication jauh lebih kompleks dibanding htpasswd karena mengharuskan kita membangun dan mengoperasikan server token sendiri yang menerbitkan JSON Web Token sesuai kebijakan akses yang ditentukan, sehingga pola ini biasanya baru dipertimbangkan saat kebutuhan kontrol akses organisasi sudah cukup rumit untuk menjustifikasi kompleksitas tambahannya.

20.4 API Registry

Distribution mengimplementasikan Registry HTTP API V2, spesifikasi resmi yang mendefinisikan seluruh interaksi antara Docker CLI dan registry lewat protokol HTTP. API yang sama ini juga bisa dipanggil langsung, misalnya lewat curl, untuk kebutuhan otomasi seperti audit isi registry atau integrasi dengan tooling internal.

20.4.1 Memeriksa Ketersediaan Registry

Endpoint dasar /v2/ dipakai Docker CLI maupun tool lain untuk memeriksa apakah sebuah alamat benar-benar registry yang mendukung API V2, sekaligus memicu proses autentikasi kalau registry mengharuskannya.

curl -u admin:passwordkuat123 https://registry.internal.perusahaan.com/v2/

Respons berupa status 200 OK dengan body JSON kosong ({}) menandakan registry aktif dan kredensial yang dipakai valid. Status 401 Unauthorized menandakan kredensial salah atau belum disertakan sama sekali.

20.4.2 Melihat Daftar Repository dan Tag

Endpoint _catalog mengembalikan daftar nama repository yang tersimpan di registry, berguna untuk Sysadmin/DevOps Engineer yang perlu audit cepat image apa saja yang pernah di-push tanpa harus membuka dashboard tambahan.

curl -u admin:passwordkuat123 https://registry.internal.perusahaan.com/v2/_catalog
{"repositories":["my-app","payment-service","worker-queue"]}

Untuk melihat tag apa saja yang tersedia pada satu repository tertentu, panggil endpoint tags/list dengan menyebutkan nama repository-nya.

curl -u admin:passwordkuat123 \
  https://registry.internal.perusahaan.com/v2/my-app/tags/list
{"name":"my-app","tags":["1.0","1.1","1.2.0","latest"]}

Kedua endpoint ini mendukung pagination lewat parameter n (jumlah item per halaman) dan last (item terakhir dari halaman sebelumnya). Parameter ini penting diperhatikan begitu jumlah repository atau tag sudah mencapai ratusan, karena tanpa pagination, satu kali panggilan API bisa memaksa registry membangun dan mengirim respons berukuran besar sekaligus.

Registry juga menerima permintaan DELETE untuk menghapus sebuah manifest, unit metadata yang menghubungkan tag dengan layer-layer penyusun image-nya. Kemampuan ini nonaktif secara default; aktifkan dulu lewat config.yml sebelum permintaan DELETE bisa diterima.

storage:
  delete:
    enabled: true

Penghapusan lewat API hanya bisa dilakukan berdasarkan digest manifest, bukan nama tag, sehingga digest-nya perlu diambil lebih dulu lewat request HEAD ke endpoint manifest sebelum menjalankan penghapusan.

curl -sI -u admin:passwordkuat123 \
  -H "Accept: application/vnd.oci.image.manifest.v1+json" \
  https://registry.internal.perusahaan.com/v2/my-app/manifests/1.0 | grep -i docker-content-digest
curl -u admin:passwordkuat123 -X DELETE \
  https://registry.internal.perusahaan.com/v2/my-app/manifests/sha256:3a5f2c8e...

20.4.3 Garbage Collection

Menghapus tag lewat API tidak otomatis membebaskan ruang disk, karena registry cuma menghapus referensi manifest ke tag tersebut sementara layer fisiknya tetap tersimpan selama masih dirujuk oleh tag lain. Untuk benar-benar membersihkan layer yang sudah tidak dirujuk tag mana pun, jalankan proses garbage collection lewat binary registry di dalam container.

docker exec registry registry garbage-collect --dry-run /etc/distribution/config.yml

Selalu jalankan dulu dengan flag --dry-run untuk melihat blob apa saja yang akan dihapus tanpa benar-benar menghapusnya, sebelum menjalankan perintah sungguhan tanpa flag tersebut. Garbage collection idealnya dijalankan saat registry dalam kondisi read-only atau tidak menerima push baru sama sekali; menjalankannya bersamaan dengan proses push yang sedang berlangsung berisiko membuat garbage collector keliru menganggap sebuah blob tidak terpakai padahal sedang dalam proses diunggah, yang berujung pada image korup.

docker exec registry registry garbage-collect /etc/distribution/config.yml

Di lapangan, Sysadmin/DevOps Engineer biasanya menjadwalkan garbage collection secara berkala lewat cron job di luar jam sibuk, dikombinasikan dengan kebijakan retensi tag otomatis (misalnya menghapus tag berbasis commit hash yang sudah lebih tua dari 90 hari) supaya ukuran storage registry tidak terus membengkak seiring bertambahnya jumlah build yang di-push setiap hari.