رفتن به محتوا
LoopX

امنیت پایه

توی این درس یاد می‌گیری کانتینرها را طوری اجرا کنی که اگر کسی به یکی‌شان نفوذ کرد، کار زیادی از او برنیاید. چهار اصل پایه را عملی می‌بینی: اجرا با کاربر غیر root (USER در Dockerfile)، نگه داشتن رازها بیرون از image (ENV و ARG لو می‌روند؛ --mount=type=secret نه)، فایل‌سیستم فقط‌خواندنی با docker run --read-only، و حذف دسترسی‌های اضافه با --cap-drop ALL. و می‌فهمی چرا --privileged و mount کردن docker.sock خطرناک‌اند. در تمرین اصلی یک کانتینر را با کمترین دسترسی اجرا می‌کنی.

مسئله: کانتینر یک جعبه‌ی امن مطلق نیست

Section titled “مسئله: کانتینر یک جعبه‌ی امن مطلق نیست”

کانتینر فقط یک پروسه‌ی جداشده روی هسته‌ی میزبان است (درس «کانتینر در برابر VM»). اگر مهاجم از طریق یک باگ در اپ تو داخل کانتینر کد اجرا کند، هر چه کانتینر اجازه دارد برای او هم مجاز است: اگر root باشد، فایل‌سیستم قابل‌نوشتن باشد و دسترسی‌های اضافی داشته باشد، دامنه‌ی خرابکاری بزرگ است. کار ما کوچک کردن این دامنه است، قبل از اینکه چیزی اتفاق بیفتد.

تشبیه: کارمند با کارت دسترسی

Section titled “تشبیه: کارمند با کارت دسترسی”

یک کارمند را با کارت دسترسی به ساختمان می‌فرستی. کارت مدیر (root) همه‌ی درها را باز می‌کند. بهتر است کارت هر کارمند فقط درهای لازمش را باز کند (غیر root)، گاوصندوق را قفل‌شده ببیند (read-only)، و کلیدهای ویژه‌ی نگهبانی را نداشته باشد (capabilities). و رمز گاوصندوق را روی کارتش ننویس (راز در image نباشد).

چهار لایه‌ی دفاع: هر کدام جدا دامنه‌ی خسارت را کم می‌کند و با هم قوی‌ترند.

مثال ۱: به‌طور پیش‌فرض root هستی

Section titled “مثال ۱: به‌طور پیش‌فرض root هستی”
Terminal window
docker run --rm alpine id
docker run --rm --user 1000:1000 alpine id
docker run --rm --user nobody alpine id
خروجی
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
uid=1000 gid=1000 groups=1000
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)

پیش‌فرض کانتینر uid=0 (root) است. با --user می‌توانی موقع اجرا کاربر دیگری بدهی؛ بهتر است این را در خود Dockerfile ثابت کنی.

⚡ بررسی سریع

اگر Dockerfile دستور USER نداشته باشد، پروسه‌ی کانتینر با کدام کاربر اجرا می‌شود؟

sec/user/Dockerfile
FROM python:3.12-alpine
RUN adduser -D -u 10001 app
WORKDIR /app
RUN chown app:app /app
COPY --chown=app:app main.py .
USER app
CMD ["python", "main.py"]
sec/user/main.py
import os
print("uid =", os.getuid())
try:
open("/etc/blocked", "w").write("x")
except OSError as e:
print("نوشتن در /etc:", e.strerror)
open("/app/ok.txt", "w").write("x")
print("نوشتن در /app/ok.txt: موفق")
Terminal window
docker build -q -t lx-sec-user sec/user >/dev/null
docker run --rm lx-sec-user
خروجی
uid = 10001
نوشتن در /etc: Permission denied
نوشتن در /app/ok.txt: موفق

اپ با uid=10001 اجرا شد: نمی‌تواند در /etc بنویسد (Permission denied) ولی در پوشه‌ی خودش می‌تواند. COPY --chown فایل را به همان کاربر می‌دهد تا مجبور نباشی chmod 777 بزنی. image های رسمی بعضی زبان‌ها یک کاربر آماده دارند و فقط کافی است USER node بنویسی:

Terminal window
docker run --rm node:22-alpine sh -c 'id node'
خروجی
uid=1000(node) gid=1000(node) groups=1000(node),1000(node)

نکته: uid عددی (مثل 10001) از اسم بهتر است، چون در بعضی ابزارها (مثل Kubernetes) اسم را نمی‌توان بررسی کرد.

مثال ۳: رازها داخل image لو می‌روند (ENV و ARG)

Section titled “مثال ۳: رازها داخل image لو می‌روند (ENV و ARG)”
sec/leak/Dockerfile
FROM alpine
ARG API_TOKEN
ENV DB_PASSWORD=s3cr3t-in-env
RUN echo "token length: ${#API_TOKEN}"
CMD ["sh"]
Terminal window
docker build -q -t lx-sec-bad --build-arg API_TOKEN=tok-in-arg sec/leak >/dev/null
echo "--- docker inspect (Env):"
docker image inspect lx-sec-bad --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -i password
echo "--- docker history:"
docker history --no-trunc lx-sec-bad --format '{{.CreatedBy}}' | grep -oE '(DB_PASSWORD|API_TOKEN)=[^ ]+' | sort -u
خروجی
--- docker inspect (Env):
DB_PASSWORD=s3cr3t-in-env
--- docker history:
API_TOKEN=tok-in-arg
DB_PASSWORD=s3cr3t-in-env

هر دو راز در متادیتای image هستند و هر کس image را بگیرد، بدون هیچ دسترسی خاصی می‌خواندشان. حتی اگر بعداً ENV را پاک کنی، لایه‌های قبلی و تاریخچه می‌مانند. قاعده: رمز، توکن و کلید را هیچ‌وقت با ENV یا ARG وارد image نکن.

مثال ۴: راز به روش درست: BuildKit secret

Section titled “مثال ۴: راز به روش درست: BuildKit secret”

برای رازی که فقط هنگام build لازم است (مثلاً توکن دانلود از یک مخزن خصوصی)، RUN --mount=type=secret فایل راز را فقط در همان دستور و به‌صورت موقت mount می‌کند و در هیچ لایه‌ای نمی‌ماند:

sec/secret/Dockerfile
FROM alpine
RUN --mount=type=secret,id=token \
echo "راز خوانده شد، طول: $(wc -c < /run/secrets/token)"
CMD ["sh", "-c", "ls /run/secrets 2>&1"]
Terminal window
cd sec/secret
printf 'sup3r-secret-token' > token.txt
docker build --progress=plain --no-cache --secret id=token,src=token.txt -t lx-sec-ok . 2>&1 | grep -E 'راز خوانده' | sed -E 's/^#[0-9]+ [0-9.]+ //'
echo "--- جست‌وجوی راز در همه‌ی لایه‌های image:"
docker save -o ../ok.tar lx-sec-ok
mkdir -p ../ok && tar -xf ../ok.tar -C ../ok
grep -rl --binary-files=text 'sup3r-secret-token' ../ok | wc -l | sed 's/^/تعداد فایل‌هایی که راز را دارند: /'
echo "--- داخل کانتینر:"
docker run --rm lx-sec-ok
خروجی
#5 [stage-0 2/2] RUN --mount=type=secret,id=token echo "راز خوانده شد، طول: $(wc -c < /run/secrets/token)"
راز خوانده شد، طول: 18
--- جست‌وجوی راز در همه‌ی لایه‌های image:
تعداد فایل‌هایی که راز را دارند: 0
--- داخل کانتینر:
ls: /run/secrets: No such file or directory

راز در build خوانده شد ولی در هیچ فایلی از image پیدا نشد، و در کانتینر هم /run/secrets خالی است. فایل token.txt هم نباید commit شود. برای رازی که در زمان اجرا لازم است (پسورد دیتابیس)، آن را در زمان docker run بده: -e یا بهتر، --env-file، یا secret در Compose/Swarm؛ در هر حال نه داخل image.

مثال ۵: فایل‌سیستم فقط‌خواندنی

Section titled “مثال ۵: فایل‌سیستم فقط‌خواندنی”
Terminal window
docker run -d --name lx-sec1 --read-only --tmpfs /tmp python:3.12-alpine sleep 60 >/dev/null
echo "نوشتن در /etc: $(docker exec lx-sec1 sh -c 'echo x > /etc/hack' 2>&1)"
echo "نوشتن در /tmp: $(docker exec lx-sec1 sh -c 'echo x > /tmp/ok && echo موفق' 2>&1)"
docker inspect lx-sec1 --format 'ReadonlyRootfs = {{.HostConfig.ReadonlyRootfs}}'
docker rm -f lx-sec1 >/dev/null
خروجی
نوشتن در /etc: sh: can't create /etc/hack: Read-only file system
نوشتن در /tmp: موفق
ReadonlyRootfs = true

با --read-only کل فایل‌سیستم ریشه فقط‌خواندنی است. مهاجم نمی‌تواند فایل اپ را عوض کند یا ابزار دانلود کند. اپ اگر باید جایی بنویسد (فایل موقت، pid، کش)، همان مسیرها را با --tmpfs (حافظه) یا یک volume مشخص قابل‌نوشتن کن. بیشتر اپ‌ها با --tmpfs /tmp کار می‌کنند.

مثال ۶: capabilities، قدرت‌های ویژه‌ی root

Section titled “مثال ۶: capabilities، قدرت‌های ویژه‌ی root”

root لینوکس به capabilityهای جدا (مثل CHOWN, NET_RAW, SYS_ADMIN) تقسیم شده. داکر به‌طور پیش‌فرض چند تا را نگه می‌دارد. ببین چه باقی مانده و چه چیزی با حذفشان از کار می‌افتد:

Terminal window
echo "پیش‌فرض: $(docker run --rm alpine sh -c 'grep CapEff /proc/self/status' | awk '{print $2}')"
echo "--cap-drop ALL: $(docker run --rm --cap-drop ALL alpine sh -c 'grep CapEff /proc/self/status' | awk '{print $2}')"
echo "--privileged: $(docker run --rm --privileged alpine sh -c 'grep CapEff /proc/self/status' | awk '{print $2}')"
echo "--- chown در حالت پیش‌فرض و بدون capability:"
docker run --rm alpine sh -c 'chown 1000 /tmp && echo "chown موفق"'
docker run --rm --cap-drop ALL alpine sh -c 'chown 1000 /tmp' 2>&1
echo "--- اضافه کردن فقط CHOWN:"
docker run --rm --cap-drop ALL --cap-add CHOWN alpine sh -c 'chown 1000 /tmp && echo "chown موفق"'
خروجی
پیش‌فرض: 00000000a80425fb
--cap-drop ALL: 0000000000000000
--privileged: 000001ffffffffff
--- chown در حالت پیش‌فرض و بدون capability:
chown موفق
chown: /tmp: Operation not permitted
--- اضافه کردن فقط CHOWN:
chown موفق

CapEff مجموعه‌ی capability های فعال است (عدد هگز). پیش‌فرض داکر چند capability دارد؛ با --cap-drop ALL صفر می‌شود و chown می‌شکند (Operation not permitted)؛ با --cap-add CHOWN فقط همان را برمی‌گردانیم. اصل least privilege: همه را بردار، فقط آنچه اپ واقعاً لازم دارد برگردان. (--privileged تقریباً همه‌ی قدرت‌ها را برمی‌گرداند.)

مثال ۷: --privileged و docker.sock خطرناک‌اند

Section titled “مثال ۷: --privileged و docker.sock خطرناک‌اند”
Terminal window
echo "دستگاه‌های قابل‌دیدن: عادی=$(docker run --rm alpine ls /dev | wc -l | tr -d ' ') privileged=$(docker run --rm --privileged alpine ls /dev | wc -l | tr -d ' ')"
خروجی
دستگاه‌های قابل‌دیدن: عادی=15 privileged=184

با --privileged کانتینر به دستگاه‌های میزبان دسترسی دارد و تقریباً جداسازی از بین می‌رود. و mount کردن سوکت داکر یعنی کنترل کامل داکر میزبان:

Terminal window
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock alpine sh -c 'apk add --no-cache curl >/dev/null 2>&1; curl -s --unix-socket /var/run/docker.sock http://localhost/version | grep -o "\"Version\":\"[^\"]*\"" | head -1'
خروجی
"Version":"29.8.1"

کانتینری که فقط یک سوکت داشت، نسخه‌ی داکر میزبان را از API پرسید؛ با همین API می‌تواند کانتینر جدید با mount کل دیسک بسازد. قاعده: docker.sock را فقط به ابزار قابل‌اعتماد بده، آن هم کم و آگاهانه؛ و --privileged را مگر برای کار خیلی خاص (مثل docker-in-docker آزمایشی) اجرا نکن.

مثال ۸: همه با هم: اجرای حداقلی

Section titled “مثال ۸: همه با هم: اجرای حداقلی”
Terminal window
docker run -d --name lx-sec2 \
--user 10001:10001 \
--read-only --tmpfs /tmp \
--cap-drop ALL \
--security-opt no-new-privileges \
--pids-limit 100 --memory 128m \
python:3.12-alpine python -m http.server 8000 >/dev/null
sleep 2
docker inspect lx-sec2 --format 'user={{.Config.User}} readonly={{.HostConfig.ReadonlyRootfs}} capdrop={{.HostConfig.CapDrop}} secopt={{.HostConfig.SecurityOpt}} pids={{.HostConfig.PidsLimit}}'
docker exec lx-sec2 python -c "import urllib.request; print('پاسخ سرور:', urllib.request.urlopen('http://localhost:8000').status)"
docker rm -f lx-sec2 >/dev/null
خروجی
user=10001:10001 readonly=true capdrop=[ALL] secopt=[no-new-privileges] pids=100
پاسخ سرور: 200

اپ با کاربر عادی، فایل‌سیستم فقط‌خواندنی، بدون هیچ capability، با no-new-privileges (جلوگیری از بالا بردن دسترسی با باینری‌های setuid) و حد روی تعداد پروسه و حافظه اجرا شد و کار می‌کند. در Compose معادل‌ها: user:, read_only: true, tmpfs:, cap_drop: [ALL], security_opt:, pids_limit:, mem_limit:.

امنیت کانتینر روی چند قابلیت هسته‌ی لینوکس ساخته شده است: namespaceها (دیدن جدا)، cgroupها (محدودیت منابع)، capabilities (شکستن قدرت root به تکه‌ها) و فیلترهای seccomp و AppArmor/SELinux (محدود کردن فراخوانی‌های سیستمی). داکر به‌طور پیش‌فرض یک پروفایل seccomp و مجموعه‌ی کوچک‌شده‌ای از capability ها اعمال می‌کند، به همین دلیل مثال ۶ در حالت پیش‌فرض عدد کوچک‌تری از --privileged داشت.

یک ظرافت مهم: root داخل کانتینر همان uid ۰ روی میزبان است (مگر user namespace یا rootless فعال باشد). یعنی اگر کانتینر به‌خاطر باگ یا تنظیم بد (--privileged، mount حساس) از جداسازی بیرون بیاید، با اختیار root میزبان بیرون می‌آید. برای همین هر لایه‌ی دفاعی جدا ارزش دارد. حالت rootless (daemon داکر هم با کاربر عادی اجرا می‌شود) این ریسک را کم می‌کند؛ نصب و تست آن به ماشین لینوکس جدا نیاز دارد و اینجا اجرا نشده (فقط معرفی).

دو نکته برای رازها:

  • متغیر محیطی در docker inspect و /proc/<pid>/environ قابل خواندن است؛ برای راز حساس، فایل mount‌شده (/run/secrets/…) بهتر است.
  • لایه‌ها تغییرناپذیرند؛ رازی که یک بار وارد image شد، حتی با پاک کردن در لایه‌ی بعد هم می‌ماند. اگر لو رفت، رمز را عوض کن (حذف image کافی نیست).
می‌خواهم… گزینه معنی
اجرا با کاربر عادی USER app / --user 10001 بدون root
فایل‌سیستم قفل --read-only + --tmpfs /tmp مسیرهای لازم قابل‌نوشتن
حذف قدرت‌های root --cap-drop ALL (+ --cap-add X) least privilege
جلوگیری از بالا بردن دسترسی --security-opt no-new-privileges setuid بی‌اثر
محدودیت پروسه/حافظه --pids-limit N / --memory 128m مقابله با fork bomb
راز زمان build RUN --mount=type=secret,id=X + --secret در image نمی‌ماند
راز زمان اجرا --env-file / secret در Compose نه داخل image
ممنوع/پرخطر چرا
--privileged تقریباً جداسازی را از بین می‌برد
mount /var/run/docker.sock کنترل کامل daemon میزبان
ENV PASSWORD=... / ARG TOKEN در متادیتای image و تاریخچه
latest + root + همه‌ی capabilityها بدترین ترکیب

۱) فراموش کردن مسیر قابل‌نوشتن با --read-only

Section titled “۱) فراموش کردن مسیر قابل‌نوشتن با --read-only”
Terminal window
docker run --rm --read-only python:3.12-alpine python -c "open('/app.log','w').write('x')" 2>&1 | tail -1
خروجی
OSError: [Errno 30] Read-only file system: '/app.log'

اپ می‌خواهد فایل بنویسد و با Read-only file system می‌میرد. راه‌حل: مسیر لازم را با --tmpfs یا volume قابل‌نوشتن کن.

۲) COPY بدون --chown و اجرا با کاربر غیر root

Section titled “۲) COPY بدون --chown و اجرا با کاربر غیر root”
Terminal window
mkdir -p m2 && printf 'FROM python:3.12-alpine\nRUN adduser -D -u 10001 app\nWORKDIR /w\nCOPY . .\nUSER app\nCMD ["python","-c","open(\\"/w/out.txt\\",\\"w\\").write(\\"x\\")"]\n' > m2/Dockerfile
docker build -q -t lx-sec-m2 m2 >/dev/null && docker run --rm lx-sec-m2 2>&1 | tail -1
docker rmi lx-sec-m2 >/dev/null
خروجی
PermissionError: [Errno 13] Permission denied: '/w/out.txt'

پوشه‌ی /w مال root است و کاربر app نمی‌تواند در آن بنویسد. راه‌حل: COPY --chown=app:app یا chown روی مسیرهای لازم.

۳) چسباندن راز در ENV «فقط موقت»

Section titled “۳) چسباندن راز در ENV «فقط موقت»”

مثال ۳: لایه‌های image همیشه می‌مانند. راه‌حل: --mount=type=secret برای build و -e/secret برای اجرا.

Terminal window
docker run --rm --cap-drop ALL nginx:alpine 2>&1 | grep -m1 'nginx: \[emerg\]'
خروجی
nginx: [emerg] chown("/var/cache/nginx/client_temp", 101) failed (1: Operation not permitted)

nginx رسمی با root شروع می‌کند، پوشه‌هایش را chown می‌کند و بعد به کاربر nginx می‌رود؛ بدون capability های CHOWN، SETUID و SETGID از همان اول می‌شکند، با خطایی که ربطی به «امنیت» به نظر نمی‌رسد (Operation not permitted). راه‌حل: خطا را بخوان و فقط همان capability ها را با --cap-add برگردان (اینجا CHOWN، SETUID، SETGID و NET_BIND_SERVICE؛ این ترکیب را تست کردم و nginx بالا آمد)، یا از imageای استفاده کن که از اول بدون root اجرا می‌شود؛ نه --privileged.

۵) تصور «کانتینر یعنی ایمن»

Section titled “۵) تصور «کانتینر یعنی ایمن»”

کانتینر هسته را با میزبان مشترک می‌خواند؛ آسیب‌پذیری هسته یا تنظیم بد می‌تواند مرز را بشکند. راه‌حل: به‌روز بودن، دفاع لایه‌ای (این درس) و اسکن آسیب‌پذیری (درس بعد).

✎ تمرینآسان

یک کانتینر alpine را با کاربر nobody اجرا کن و نشان بده نمی‌تواند در /etc بنویسد.

دیدن جواب
Terminal window
docker run --rm --user nobody alpine sh -c 'id; echo x > /etc/test 2>&1 || true'
خروجی
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
sh: can't create /etc/test: Permission denied
✎ تمرینمتوسط

تمرین اصلی: یک کانتینر را با کمترین دسترسی اجرا کن. python:3.12-alpine را با کاربر 10001، فایل‌سیستم فقط‌خواندنی، /tmp قابل‌نوشتن، بدون capability، با no-new-privileges اجرا کن و با یک اسکریپت بررسی کن که هر پنج ویژگی واقعاً فعال است (PASS/FAIL).

دیدن جواب
Terminal window
docker run -d --name lx-sec3 --user 10001:10001 --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges python:3.12-alpine sleep 60 >/dev/null
check() { if [ "$2" = "$3" ]; then echo "PASS $1"; else echo "FAIL $1 (got: $2)"; fi; }
check "کاربر غیر root" "$(docker exec lx-sec3 id -u)" "10001"
check "فایل‌سیستم فقط‌خواندنی" "$(docker inspect lx-sec3 --format '{{.HostConfig.ReadonlyRootfs}}')" "true"
check "بدون capability" "$(docker exec lx-sec3 sh -c 'grep CapEff /proc/self/status' | awk '{print $2}')" "0000000000000000"
check "no-new-privileges" "$(docker inspect lx-sec3 --format '{{.HostConfig.SecurityOpt}}')" "[no-new-privileges]"
check "/tmp قابل‌نوشتن" "$(docker exec lx-sec3 sh -c 'echo x > /tmp/f && echo ok')" "ok"
docker rm -f lx-sec3 >/dev/null
خروجی
PASS کاربر غیر root
PASS فایل‌سیستم فقط‌خواندنی
PASS بدون capability
PASS no-new-privileges
PASS /tmp قابل‌نوشتن
✎ تمرینسخت

ثابت کن یک راز BuildKit در image نمی‌ماند ولی یک ENV می‌ماند. دو image بساز (یکی با ENV TOKEN=... و یکی با --mount=type=secret) و در هر دو، با docker save و grep -r، به‌دنبال راز بگرد.

دیدن جواب
Terminal window
mkdir -p e3 && cd e3
printf 'FROM alpine\nENV TOKEN=needle-12345\n' > env.Dockerfile
printf 'FROM alpine\nRUN --mount=type=secret,id=t wc -c < /run/secrets/t >/dev/null\n' > sec.Dockerfile
printf 'needle-12345' > t.txt
docker build -q -f env.Dockerfile -t lx-e3-env . >/dev/null
docker build -q -f sec.Dockerfile --secret id=t,src=t.txt -t lx-e3-sec . >/dev/null
for i in env sec; do
docker save -o $i.tar lx-e3-$i; mkdir -p x-$i; tar -xf $i.tar -C x-$i
echo "$i: $(grep -rl --binary-files=text needle-12345 x-$i | wc -l | tr -d ' ') فایل شامل راز"
done
docker rmi lx-e3-env lx-e3-sec >/dev/null
خروجی
env: 1 فایل شامل راز
sec: 0 فایل شامل راز
؟ آزمونک
  1. اگر در Dockerfile دستور USER نباشد، پروسه با چه کاربری اجرا می‌شود؟

  2. چرا ENV PASSWORD=... در Dockerfile خطرناک است؟

  3. docker run --read-only چه می‌کند؟

  4. اصل least privilege برای capabilities یعنی…

  5. چرا mount کردن /var/run/docker.sock خطرناک است؟

  6. no-new-privileges چه می‌کند؟

  • کاربر غیر root: USER app (ترجیحاً uid عددی) و COPY --chown؛ بدون USER، root است.
  • رازها هرگز در ENV/ARG/لایه‌ی image؛ زمان build: --mount=type=secret؛ زمان اجرا: env-file یا secret.
  • --read-only با --tmpfs برای مسیرهای موقت؛ مسیرهای قابل‌نوشتن را مشخص کن.
  • --cap-drop ALL و فقط capability لازم را با --cap-add؛ هرگز --privileged و docker.sock را بی‌حساب نده.
  • --security-opt no-new-privileges، --pids-limit، --memory لایه‌های دفاعی ارزان‌اند.
  • رازی که لو رفت را عوض کن؛ حذف image کافی نیست.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
USER appاجرای پروسه با کاربر غیر root
COPY --chown=app:app . .فایل‌ها مال کاربر اپ
docker run --user 10001:10001 IMGکاربر در زمان اجرا
docker run --read-only --tmpfs /tmp IMGفایل‌سیستم فقط‌خواندنی
docker run --cap-drop ALL --cap-add CHOWN IMGحداقل capability
docker run --security-opt no-new-privileges IMGجلوگیری از افزایش دسترسی
RUN --mount=type=secret,id=t …راز زمان build (در image نمی‌ماند)
docker build --secret id=t,src=file .دادن راز به build
docker run --pids-limit 100 --memory 128m IMGحد پروسه و حافظه