کانتینر پیکربندی و فرایند Nginx را بسته‌بندی می‌کند، ولی trust و امنیت میزبان همچنان اهمیت دارند. امشب یک سایت ساده را با filesystem فقط‌خواندنی و worker بدون root اجرا می‌کنیم.

این جلسه بخشی از دورهٔ انجین‌اکس در ۵۰ شب است. قرارداد آزمایشگاه، مسیرها و روال اعمال تغییر در راهنمای دوره آمده است.

محیط: VM جدا با Docker Engine و Compose plugin نصب‌شده از راهنمای رسمی توزیع؛ پورت میزبان 18080 آزاد است و آزمایش محلی انجام می‌شود. عضویت docker group معمولاً قدرت مدیریتی میزبان می‌دهد؛ non-root داخل کانتینر با rootless daemon متفاوت است.

image و config ثبت‌شده#

در ~/nginx-lab/container پوشه‌های conf و content بساز و یک index.html با متن قابل تشخیص قرار بده. image رسمی را دریافت و digest واقعی را ثبت کن:

docker pull nginx:stable-alpine
docker image inspect nginx:stable-alpine --format '{{index .RepoDigests 0}}'

متغیر NGINX_IMAGE را به همان nginx@sha256:... واقعی تنظیم کن؛ سه‌نقطه مقدار قابل اجرا نیست. digest ثبات artifact را می‌دهد، اما برای patch امنیتی باید آگاهانه digest جدید انتخاب کرد. با اجرای id nginx داخل همان image، UID/GID را ببین؛ نمونهٔ زیر UID/GID برابر 101 فرض می‌کند و در صورت تفاوت باید تمام user/tmpfsها یکدست تغییر کنند.

فایل conf/nginx.conf کامل است؛ entrypoint پیش‌فرض image را کنار می‌گذاریم تا config readonly را تغییر ندهد:

worker_processes auto;
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events { worker_connections 256; }
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    access_log /dev/stdout;
    client_body_temp_path /tmp/client_body;
    proxy_temp_path /tmp/proxy;
    fastcgi_temp_path /tmp/fastcgi;
    uwsgi_temp_path /tmp/uwsgi;
    scgi_temp_path /tmp/scgi;
    server {
        listen 8080;
        server_name _;
        root /usr/share/nginx/html;
        location = /healthz { return 200 "ok\n"; }
        location / { try_files $uri $uri/ =404; }
    }
}

پورت داخلی 8080 به capability مربوط به پورت‌های پایین نیاز ندارد. owner کد و config میزبان خودت باشد و mode فایل‌ها 644 و پوشه‌ها 755؛ worker اجازهٔ نوشتن آن‌ها ندارد.

docker run -d --name nginx-lab-static \
  --user 101:101 --read-only --cap-drop ALL \
  --security-opt no-new-privileges:true --pids-limit 100 --memory 256m \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m,uid=101,gid=101,mode=1770 \
  -p 127.0.0.1:18080:8080 \
  -v "$PWD/conf/nginx.conf:/etc/nginx/nginx.conf:ro,Z" \
  -v "$PWD/content:/usr/share/nginx/html:ro,Z" \
  --entrypoint nginx "$NGINX_IMAGE" -g 'daemon off;'
curl --fail http://127.0.0.1:18080/
docker exec nginx-lab-static nginx -t
docker top nginx-lab-static
docker logs --tail 30 nginx-lab-static

:Z برای bind mountهای اختصاصی SELinux است؛ فقط پوشهٔ همین آزمایشگاه را relabel کن، نه /etc/nginx یا مسیر تولید را. در میزبان بدون SELinux نیازی به آن نیست. برای مسیر مشترک چند کانتینر، label مشترک را آگاهانه انتخاب کن.

نتیجه و بازگشت#

صفحه باید از localhost باز شود و از کلاینت بیرونی روی IP شبکهٔ میزبان همین پورت باز نشود. با docker inspect، User، ReadonlyRootfs، CapDrop، mountهای readonly و bind address را بررسی کن. از داخل کانتینر نوشتن در config باید خطای permission/readonly بدهد؛ /tmp برای state موقت قابل نوشتن است.

بازگشت: docker stop nginx-lab-static و سپس docker rm nginx-lab-static؛ فقط کانتینر نام‌دار همین تمرین حذف می‌شود، فایل‌های میزبان باقی می‌مانند. Docker socket را به کانتینر وب mount نکن.

منبع: image رسمی Nginx ، گزینه‌های docker run ، نصب Nginx با Docker .