WIUNIX ACADEMY
راهاندازی رجیستری خصوصی Docker با HTTPS و Nginx
صفحهٔ 2 از 5؛ مراحل قبلی و پیشنیازها را پیش از ادامه مرور کن.
آموزش متناسب با محیط تو
کجا اجرا میکنی؟
سیستمعامل و نسخه را انتخاب کن تا مراحل، دستورها و راهحلها برای همان محیط نمایش داده شوند.
آموزش متناسب با مقادیر تو
تنظیمات این آموزش
مقادیر نمونه را برای محیط خودت تنظیم کن؛ متن و دستورهای مرتبط با هم تغییر میکنند. رمز، کلید خصوصی و توکن وارد نکن. مقادیر فقط در همین زبانه نگهداری میشوند و به سرور یا لینک اشتراکگذاری فرستاده نمیشوند. پیش از اجرا، DNS، گواهیها و سیاست شبکهٔ واقعی را بررسی کن.
آیپی، دامنه یا پورت مثالها با محیط تو فرق دارد؟ دکمهٔ زیر را باز کن، مقادیر را تغییر بده و «اعمال در آموزش» را بزن؛ سپس دستورهای بهروزشده را کپی کن.
تنظیم مقادیر برای محیط من8 مقدار قابلتغییر
قدم بعدی
فایل bcrypt بومی رجیستری را آماده کنید
رمز از stdin به htpasswd میرسد و در argv قرار نمیگیرد. خروجی bcrypt مستقیم در فایل تازه و محافظتشده نوشته میشود؛ رمز و هش چاپ نمیشوند. ورودی خالی یا نامعتبر متوقف میشود. حذف فایل موقت تضمین پاکشدن فیزیکی از SSD یا اسنپشات نیست. رجیستری فایل کاربران را هنگام شروع میخواند؛ تعویض رمز به restart کنترلشده نیاز دارد.
VM رجیستری
sudo python3 - <<'PY'
import os, stat, subprocess
from pathlib import Path
p=Path('/srv/academy-registry/auth/password-input')
s=p.lstat(); assert stat.S_ISREG(s.st_mode) and s.st_uid==0 and stat.S_IMODE(s.st_mode)==0o600
secret=p.read_bytes(); assert secret.endswith(b'\n') and secret.count(b'\n')==1
assert 20 <= len(secret.rstrip(b'\n')) <= 72, 'Use 20..72 ASCII bytes'
assert all(33<=c<=126 for c in secret.rstrip(b'\n')), 'Printable ASCII, no spaces'
result=subprocess.run(['htpasswd','-nBi','-C','12','academy-publisher'],input=secret,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,check=True)
assert result.stdout.startswith(b'academy-publisher:$2')
f=Path('/srv/academy-registry/auth/htpasswd')
fd=os.open(f,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o640)
with os.fdopen(fd,'wb') as out: out.write(result.stdout)
os.chown(f,0,23153); os.chmod(f,0o640)
p.unlink()
print('bcrypt file created; plaintext input removed (not a secure-erasure guarantee)')
PY
فایل bcrypt با مجوز 0640 ساخته و ورودی موقت حذف میشود.
قدم بعدی
فایلهای پیکربندی را در ویرایشگر باز کنید
دو فایل تازه روی VM باز میشوند. دو پنل بعدی محتوای فایلاند و باید در فایل متناظر ذخیره شوند؛ فرمان ترمینال را داخل YAML ننویسید. secret امضای وضعیت آپلود در گام بعد، بدون نمایش، اضافه خواهد شد.
VM رجیستری
sudo python3 - <<'PY'
import os
for name in ('config/config.yml','config/nginx.conf'):
f='/srv/academy-registry/'+name
fd=os.open(f,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
os.close(fd)
PY
sudo nano '/srv/academy-registry/config/config.yml' '/srv/academy-registry/config/nginx.conf'
محتوای هر دو پنل در فایل درست ذخیره میشود.
فایل تنظیمات Distribution
بکاند داخل شبکهٔ کانتینر گوش میدهد و فقط نگاشت loopback میزبان منتشر میشود. host ثابت باعث میشود نشانی ادامهٔ آپلود به دامنهٔ HTTPS عمومی اشاره کند. حذف و پاکسازی خودکار آپلود ناقص در این پایلوت خاموش است؛ فضای مصرفشده را پایش کنید.
VM رجیستری
version: 0.1
log:
level: error
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: false
maintenance:
uploadpurging:
enabled: false
readonly:
enabled: false
http:
addr: 0.0.0.0:5000
host: https://registry.example.net
relativeurls: false
draintimeout: 60s
auth:
htpasswd:
realm: academy-registry
path: /auth/htpasswd
در نام فایل نمایشدادهشده ذخیره کنید؛ تست syntax و شروع سرویس در گامهای بعد استفادهٔ آن را بررسی میکند.
فایل پراکسی HTTPS
احراز هویت فقط در Distribution انجام میشود؛ Nginx هدر Authorization را عبور میدهد، هدرهای اعتماد کلاینت را جایگزین میکند و Host دیگر را رد میکند. اگر یک پراکسی دیگر جلوی آن قرار میدهید، مرز اعتماد IP را جدا طراحی کنید.
حد 1024 مگابایت برای هر درخواست HTTP است، نه مجموع اندازهٔ یک ایمیج چندلایه. خاموشبودن buffering همراه HTTP/1.1 برای ارسال chunked مهم است. مقدار 900 ثانیه زمان بیکاری است، نه مهلت کل انتقال. آپلود قطعشده را با کلاینت دوباره امتحان کنید؛ پراکسی نباید بدنهٔ نیمهارسالشده را خودکار به بکاند دیگری بفرستد.
VM رجیستری
server {
listen 443 ssl;
server_name registry.example.net;
if ($host != registry.example.net) { return 421; }
ssl_certificate /srv/academy-registry/tls/fullchain.pem;
ssl_certificate_key /srv/academy-registry/tls/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 1024m;
client_body_timeout 900s;
access_log off;
error_log /var/log/nginx/academy-registry-error.log error;
location /v2/ {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host registry.example.net;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host registry.example.net;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Forwarded "";
proxy_set_header Connection "";
proxy_request_buffering off;
proxy_buffering off;
proxy_connect_timeout 5s;
proxy_read_timeout 900s;
proxy_send_timeout 900s;
proxy_next_upstream off;
}
location / { return 404; }
}
در نام فایل نمایشدادهشده ذخیره کنید؛ تست syntax و شروع سرویس در گامهای بعد استفادهٔ آن را بررسی میکند.
قدم بعدی
secret داخلی آپلود را بدون نمایش اضافه کنید
پس از ذخیرهٔ YAML فقط یک بار اجرا کنید. مقدار تصادفی مستقیم وارد فایل محافظتشده میشود و چاپ نمیشود. این فایل باید همراه بکاپ تنظیمات نگهداری شود. تغییر secret وضعیت آپلودهای در جریان را نامعتبر میکند؛ آن را در لاگ یا Git قرار ندهید.
VM رجیستری
sudo python3 - <<'PY'
import os, secrets
from pathlib import Path
p=Path('/srv/academy-registry/config/config.yml')
text=p.read_text(encoding='utf-8')
assert text.count('http:\n')==1 and ' secret:' not in text
text=text.replace('http:\n','http:\n secret: '+secrets.token_hex(32)+'\n',1)
p.write_text(text,encoding='utf-8')
os.chown(p,0,23153); os.chmod(p,0o640)
print('Persistent upload-state signing secret generated privately')
PY
secret پایدار در فایل 0640 ثبت میشود و در خروجی دیده نمیشود.
قدم بعدی
تصویر پینشده را فقط روی loopback اجرا کنید
بازبینی ۲۰۲۶-۱۰-۲۱ بر پایهٔ Distribution 3.1.2 است. digest فهرست چندمعماری تصویر رسمی از Docker Hub خوانده و دریافت آن بررسی شد. RepoDigests دریافتی را با مرجع کامل پنل مقایسه کنید؛ latest جایگزین آن نیست. پینبودن، بهروزرسانی امنیتی خودکار ایجاد نمیکند.
پردازش با شناسهٔ عددی 23153 اجرا میشود، ریشهٔ کانتینر فقطخواندنی است و capability و سوکت Docker ندارد. فقط دادهها قابل نوشتناند. برای این پایلوت لاگ کانتینر خاموش است تا بهویژه رفتار ساخت کاربر در نبود htpasswd رمزی چاپ نکند. قبل از بهرهبرداری، مانیتورینگ محافظتشده اضافه کنید. نشانی HTTP محلی فقط برای عیبیابی روی همین VM است.
VM رجیستری
set -eu
sudo test -s '/srv/academy-registry/auth/htpasswd'
sudo test -s '/srv/academy-registry/config/config.yml'
sudo docker pull 'docker.io/library/registry:3.1.2@sha256:ddf754342cfc8acc51a56d5d0ab6af06826461864460636d8bd5c546dab2a7b8'
sudo docker image inspect 'docker.io/library/registry:3.1.2@sha256:ddf754342cfc8acc51a56d5d0ab6af06826461864460636d8bd5c546dab2a7b8' --format '{{json .RepoDigests}}'
sudo docker run -d --name 'academy-registry' --restart unless-stopped \
--user '23153:23153' \
--read-only --cap-drop ALL --security-opt no-new-privileges \
--log-driver none \
-p '127.0.0.1:5000:5000' \
--mount 'type=bind,src=/srv/academy-registry/data,dst=/var/lib/registry' \
--mount 'type=bind,src=/srv/academy-registry/auth,dst=/auth,readonly' \
--mount 'type=bind,src=/srv/academy-registry/config,dst=/etc/distribution,readonly' \
'docker.io/library/registry:3.1.2@sha256:ddf754342cfc8acc51a56d5d0ab6af06826461864460636d8bd5c546dab2a7b8' /etc/distribution/config.yml
ready=no
for attempt in $(seq 1 30); do
status=$(curl --silent --show-error --max-time 2 --output /dev/null --write-out '%{http_code}' 'http://127.0.0.1:5000/v2/' || true)
if test "$status" = 401; then ready=yes; break; fi
sleep 1
done
test "$ready" = yes
printf 'Loopback /v2/: 401\n'
sudo ss -lntp
پاسخ محلی 401 است و نگاشت میزبان فقط روی 127.0.0.1:5000 دیده میشود.