dockerfile

بهترین روش‌ های نوشتن Dockerfile

فهرست مطالب

Docker یک پلتفرم مجازی ساز سطح بالا است که به شما امکان می‌دهد نرم افزار را به صورت واحدهای جداگانه‌ای به نام کانتینر (Container) بسته بندی کنید. کانتینرهای داکر از ایمیج‌هایی (Image یا تصاویر) ساخته می‌شوند که شامل موارد مورد نیاز برای اجرای حجم کاری، مانند فایل‌های باینری و کتابخانه‌ها است. این تصاویر یا ایمیج‌های داکر مانند اسکریپت‌های متوالی هستند که برای مونتاژ، در فایلی به نام Dockerfile تعریف می‌شوند. داکرفایل‌ها می‌توانند شامل چندین نوع دستورالعمل از RUN برای اجرای یک دستور تا COPY برای افزودن فایل‌ها از میزبان شما باشند. در ادامه با بهترین روش‌های DockerFile آشنا خواهید شد.

رعایت این دستورالعمل‌ها به شما کمک می‌کند تا از مزایای کانتینرسازی بهره مند شوید و در عین حال خطرات مربوط به مسائل امنیتی و عملکردی را به حداقل برسانید.

dockerfile-best-practices

Dockerfile دقیقا چه کاری انجام می‌‎دهد؟

با ساخت Dockerfile می‌‌توانید از مزایای بسیاری بهره ببرید. داکرفایل‌ها مجموعه‌ای از دستورالعمل‌‎ها هستند که از آن‌ها برای ساخت ایمیج داکر استفاده می‌شود. داکرفایل بر اساس مجموعه‌ای از کلمات کلیدی بنا شده که به شما اجازه می‌دهد سیستم فایل یک کانتینر را دستکاری کنید. هر کلمه کلیدی با حروف بزرگ نوشته می‌شود و به دنبال آن Argumentهای خاصی قرار می‌گیرد. 

				
					FROM ubuntu:latest
COPY /source /destination
RUN touch /example-demo
				
			

سه دستورالعمل خاص در کد بالا استفاده شده است:

  • FROM: که از آن برای استفاده از یک تصویر (Image) پایه برای دستور شما استفاده می‌شود. در نمونه کد بالا از جدیدترین نسخه سیستم عامل Ubuntu استفاده شده است. 
  • COPY: به شما اجازه می‌دهد فایل‌ها را از سیستم خود به داخل ایمیج (در این مورد Ubuntu) کپی کنید.
  • RUN: برای اجرای دستورها داخل ایمیج، از RUN استفاده می‌کنیم.

بهترین روش‌های Dockerfile

نوشتن یک Dockerfile فراتر از نمونه‎‌ای است که در کد بالا به شما نشان دادیم. یک Dockerfile بهینه، ایمیج‌هایی سریع‌تر و امن‌تر تولید می‌کند. در ادامه حیاتی‌ترین روش‌ها برای نوشتن Dockerfile را مورد بررسی قرار می‌دهیم.

امنیت Dockerfile

امنیت در دنیای کانتینرها حرف اول را‌ می‌زند. با بهترین روش‌های نوشتن Dockerfile، امنیت ایمیج خود را به حداکثر برسانید.

1. اجرای کانتینر به عنوان کاربر غیر Root

یکی از توصیه‌های مهم امنیتی، اجرای کانتینرها به عنوان یک کاربر غیر ریشه است. بر اساس گزارش‌ها، درصد بالایی از ایمیج‌ها همچنان با کاربر root اجرا می‌شوند. اجرا با دسترسی ریشه یک ریسک امنیتی بزرگ است. اگر مهاجمی بتواند از برنامه شما سو استفاده کند، به تمام دسترسی‌های root در داخل کانتینر دست پیدا می‌‎کند. بنابراین همیشه یک کاربر و گروه مشخص ایجاد کنید و با دستور USER به آن سوئیچ کنید.

				
					# 1️⃣ Base image: lightweight Alpine Linux
FROM alpine:3.18

# 2️⃣ Create non-root user and group
RUN addgroup -S myappgroup && adduser -S myappuser -G myappgroup

# 3️⃣ Copy project files and set ownership
COPY --chown=myappuser:myappgroup . /app

# 4️⃣ Switch to non-root user
USER myappuser

# 5️⃣ Default command to run the application
CMD ["./my-app"]

				
			

در کد بالا به ترتیب این کارها انجام می‌شود:

  • مرحله 1: از تصویر پایه alpline linux استفاده شده که به دلیل سبک و کم حجم بودن آن برای ساخت imageهای کوچک‌تر مناسب است
  • مرحله 2: با دستور addgroup یک گروه با نام myappgroup ساخته می‌شود. با دستور adduser یک کاربر غیر روت به نام myappuser تولید می‌شود. 
  • مرحله 3: با دستور COPY، همه فایل‌های موجود در فولدر فعلی به داخل app/ انتقال داده می‌شود. همچنین مالک فایل نیز در این دستور بر روی کاربر myappuser تنظیم می‌شود.
  • مرحله 4: با دستور USER، کاربر کانتینر به کاربر ساخته شده تغییر داده می‌شود.
  • مرحله 5: از دستور این مرحله، برای اجرای برنامه my-app در داخل مسیر app/ استفاده می‌شود.

2. مدیریت صحیح اطلاعات حساس

هرگز اطلاعات حساس مانند پسوردها و APIها را مستقیما در Dockerfile یا به عنوان متغیر محیطی قرار ندهید. 

  • از متغیرهای محیطی استفاده نکنید: هرکسی به کانتینر دسترسی داشته باشد، می‌تواند با docker inspect متغیرهای محیطی را بخواند.
  • از ابزارهای مدیریت Secrets استفاده کنید: راه حل صحیح، استفاده از سیستم مدیریت Secrets مانند Docker Secrets است که این اطلاعات را به صورت امن در زمان اجرا به کانتینر تزریق می‌کنند.
  • از ARG برای Build-time Secrets استفاده نکنید: اگرچه ARGها در ایمیج نهایی باقی نمی‌مانند، اما در docker history قابل مشاهده هستند. نمونه‌ای از استفاده Build-time secret در Dockerfile:
				
					# -----------------------------
# 1️⃣ Base image
# -----------------------------
FROM alpine:3.18

# -----------------------------
# 2️⃣ Copy application files
# -----------------------------
COPY app.py /app/app.py

# -----------------------------
# 3️⃣ Use build-time secret securely
# -----------------------------
RUN --mount=type=secret,id=mysecret \
    sh -c 'PASSWORD=$(cat /run/secrets/mysecret) && echo "Password is used here"'

# -----------------------------
# 4️⃣ Default command
# -----------------------------
CMD ["sh"]

				
			

3. اسکن ایمیج برای آسیب پذیری‌ها

Dockerfile شما پایه و اساس ایمیج است. پس باید آن را برای آسیب پذیری‌ها اسکن کنید:

  • اسکن در CI/CD: ایمیج‌های خود را در پایپ لاین برای یافتن آسیب پذیری‌های شناخته شده اسکن کنید. ابزارهایی مانند Grype و Sydig Secure برای این کار ساخته شده‌اند.
  • اسکن Dockerfile: برخی ابزارها می‌‎توانند خود Dockerfile را برای تنظیمات ناامن تحلیل کنند.
				
					name: Lint Dockerfile
on: [push, pull_request]

jobs:
  hadolint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run hadolint
        uses: hadolint/hadolint-action@v2
        with:
          dockerfile: Dockerfile

				
			

4. استفاده از ایمیج‌های پایه قابل اعتماد

همیشه ایمیج‌های پایه خود را از منابع معتبر مانند رجیستری‌های رسمی دریافت کنید. استفاده از ایمیج‌های ناشناس Docker Hub می‌تواند حاوی بدافزار یا پیکربندی‌های ناامن باشد.

5. عدم نصب ابزارهای غیر ضروری

هر ابزار اضافه‌ای می‌تواند توسط مهاجم برای  پیشبرد حملات استفاده شود. ایمیج را حد امکان مینیمال و خلوت نگه دارید. 

پس از ساخت Dockerfile و آماده سازی ایمیج، نوبت به اجرای کانتینرها در محیطی پایدار و سریع می‌رسد. اگر به دنبال بستری امن و همیشه در دسترس برای تست و استقرار پروژه‌های داکر هستید، می‌توانید از سرورهای مجازی ایران پویان آی تی استفاده کنید. سرورهای ایران با پینگ پایین، گزینه‌ای عالی برای توسعه دهندگان و تیم‌های DevOps هستند.

خرید سرور مجازی ایران – راه اندازی و پشتیبانی سریع

بهینه سازی ایمیج Dockerfile

ایمیج‌های کوچکتر سریع‌تر منتقل می‌شوند و سطح حمله کمتری دارند.

1. استفاده از ساخت چندمرحله‌ای

بهترین روش برای کاهش شدید حجم ایمیج استفاده از Multi-Stage Build است. شما برای کامپایل کد به ابزارهای سنگینی نیاز دارید، اما در زمان اجرا فقط به فایل باینری یا فایل dist نیاز است. 

				
					# ----- Stage 1: Builder -----
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .

				
			
				
					# ----- Stage 2: Final -----
FROM alpine:latest
WORKDIR /app
COPY --from=builder /src/myapp .
CMD ["./myapp"]

				
			

2. استفاده از ایمیج‌های پایه کوچک

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

  • Alpine: بسیار محبوب و کم حجم است. اما از libc musl استفاده می‌کند که ممکن است با برخی برنامه‌های مبتنی بر glibc ناسازگار باشد.
  • Distroless: ایمیج‌های Distroless فقط شامل برنامه شما و وابستگی‌های زمان اجرای آن هستند. این ایمیج‌ها به دلیل نداشتن پکیج منیجر یا شل، از نظر امنیتی فوق العاده است.
  • Slim: نسخه‌‎های slim یک حد وسط خوب هستند که ابزارهای غیر ضروری را حذف کرده‌اند اما همچنان بر پایه دبیان یا اوبونتو هستند.

3. استفاده هوشمندانه از فایل dockerignore.

یک فایل dockerignore ایجاد کنید تا از ارسال فایل‌های غیرضروری به Build Context جلوگیری کنید. این کار هم امنیت و هم سرعت بیلد را افزایش می‌دهد. برای مثال فرض کنید یک پروژه Node.js با ساختار زیر دارید:

				
					myapp/
├── .git/
├── node_modules/
├── .env
├── Dockerfile
├── package.json
├── package-lock.json
└── app.js
				
			

فرض کنید برای پروژه‌ای با ساختار بالا یک Dockerfile با فرم زیر دارید.

				
					FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "app.js"]

				
			

اگر یک فایل dockerignore نداشته باشید، داکر کل پوشه حتی بخش git. را ارسال خواهد کرد، برای جلوگیری از این مورد باید یک فایل dockerignore. مانند فایل زیر بسازید:

				
					# Node.js dependencies
node_modules

# Git repo files
.git
.gitignore

# Environment variables (sensitive)
.env

# Logs and temporary files
*.log
tmp/
*.tmp

# OS / editor files
.DS_Store
.vscode/
.idea/

# Docker files themselves (optional)
Dockerfile
docker-compose.yml

				
			

4. پاکسازی کش و فایل‌های موقت در همان لایه

اگر مجبور به نصب پکیج‌ها با apt یا apk هستید، کش آن‌ها را در همان دستور RUN پاک کنید تا در لایه ایمیج باقی نماند.

				
					FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    curl \
    wget \
    && rm -rf /var/lib/apt/lists/*

				
			

بیشتر بخوانید!

آموزش نصب Docker در Ubuntu

بهینه سازی سرعت ساخت Dockerfile

سرعت ساخت (Build) بالا با سرعت بالای CI/CD یا همان چرخه‌های توسعه رابطه‌ای مستقیم دارد.

1. ادغام دستورات RUN

هر دستور RUN یک لایه جدید ایجاد می‌کند. برای کاهش لایه‌ها و مرتب سازی، دستورات مرتبط شل را با && و / ادغام کنید.

				
					FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    gcc \
    libpq-dev \
    && pip install --no-cache-dir flask psycopg2 \
    && rm -rf /var/lib/apt/lists/*
				
			

2. استفاده از COPY به جای ADD

یکی از قوانین داکرنویسی حرفه‌ای این است، همیشه از COPY استفاده کنید، مگر اینکه دقیقا به قابلیت استخراج خودکار tar نیاز داشته باشید. COPY شفاف‌تر و قابل پیش بینی‌تر است.

				
					# Use official Node.js image
FROM node:18

# Create app directory
WORKDIR /usr/src/app

# Copy package files first for caching
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy rest of the application
COPY . .

# Run the app
CMD ["npm", "start"]

				
			

پایداری و نگهداری در Dockerfile

هدف، نوشتن Dockerfile به صورتی است که به راحتی در آینده قابل به روزرسانی باشد و در محیط‌های مختلف پایدار عمل کند.

1. استفاده از تگ‌های مشخص

هرگز از تگ latest در ایمیج‌های پایه در محیط پروداکشن استفاده نکنید. این تگ متغیر است و باعث می‌شود بیلدهای شما غیر قابل تکرار باشند. 

				
					FROM alpine:3.18.4

RUN apk add --no-cache curl
COPY . /app
CMD ["sh", "/app/start.sh"]
				
			

2. استفاده از WOKDIR به جای cd

همیشه از WORKDIR برای تغییر دایرکتوری کاری خود استفاده کنید. این کار خوانایی را افزایش می‌دهد از و خطاهای ناشی از cd جلوگیری می‌کند.

3. تعریف CMD و ENTRYPOINT به صورت JSON

همیشه سعی کنید از فرمت آرایه JSON به جای رشته‌های متنی استفاده کنید. اگر کانتینر سیگنال‌هایی مانند SIGTERM دریافت کند در حالت استفاده از رشته‌های متنی به برنامه node نمی‌رسند بلکه به خود شل می‌رسند، در نتیجه برنامه ممکن است درست بسته نشده و فایل‌ها را ذخیره نکند. روش درست:

				
					FROM node:18
WORKDIR /app
COPY . .

CMD ["node", "index.js"]

				
			

4. استفاده از HEALTHCHECK

دستور HEALTHCHECK به داکر اجازه می‌دهد تا وضعیت سلامت کامل برنامه شما را بررسی کند.

				
					FROM node:18-alpine

WORKDIR /app
COPY . .

RUN npm install

CMD ["npm", "start"]

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

				
			

5. افزودن Metadata با LABEL

از LABEL برای افزودن اطلاعات مفید مانند مالک، نسخه، لینک به سورس کد و ایمیج خود استفاده کنید.

				
					FROM python:3.12-slim

LABEL maintainer="Amir Norozi <amir@example.com>" \
      version="1.0.0" \
      description="A lightweight Python API for text processing" \
      org.opencontainers.image.source="https://github.com/amirnorozi/text-api" \
      org.opencontainers.image.licenses="MIT"

WORKDIR /app
COPY . .

RUN pip install -r requirements.txt

CMD ["python", "app.py"]

				
			

تنظیمات پیشرفته dockerfile

در ادامه برخی تنظیمات پیشرفته و کم‌تر شناخته شده در Dockerfile را مورد بررسی قرار می‌‎دهیم.

1. تنظیم محدودیت منابع

اگرچه این مورد مستقیما در Dockerfile تنظیم نمی‌شود، اما طراحی Dockerfile شما باید همسو با اجرای کانتینرها با محدودیت‌های memory و cpu باشد تا از مصرف بی رویه منابع جلوگیری شود. مثال طراحی سبک در Dockerfile:

				
					FROM python:3.12-alpine

WORKDIR /app
COPY . .

RUN pip install --no-cache-dir -r requirements.txt

CMD ["python", "app.py"]

				
			

نمونه‌ای از تنظیم محدودیت منابع در هنگام اجرای کانتینر:

				
					docker run -d \
  --name myapp \
  --memory="256m" \
  --cpus="0.5" \
  myapp:latest

				
			

2. محدود کردن دسترسی‌ها

در زمان اجرا، اطمینان حاصل کنید که کانتینرها با حداقل دسترسی‌های لازم اجرا می‌شوند. از –privileged دوری کنید و capabilities یا قابلیت‌های لینوکس را تا حد امکان پایین بیاورید.

3. استفاده از linters

از ابزارهایی مانند hadolint برای بررسی خودکار Dockerfile خود در برابر بهترین روش‌ها استفاده کنید. می‌توانید پس از نصب hadolint آن را با دستور زیر روی dockerfile اجرا کنید.

				
					hadolint Dockerfile

				
			

برای مثال، فرض کنید Dockerfile زیر را دارید:

				
					FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl
USER root
CMD ["bash"]

				
			

با اجرای hadolint بر روی این داکرفایل، احتمالا با خروجی و خطاهای زیر برخورد خواهید داشت:

				
					DL3008: Pin versions in apt-get install. 
DL3002: Last user should not be root.
DL3006: Always tag the version of an image explicitly.

				
			

نتیجه گیری

با مجموعه توصیاتی که در این مقاله به شما آموزش دادیم می‌توانید موضوعات پیچیده و حیاتی داکرفایل‌ها را که نادیده گرفتن آن می‌تواند عواقب بدی داشته باشد را بهتر درک کنید. از امنیت گرفته تا بهبود پایداری Dockerfile، همه به شما کمک خواهند کرد تا محیطی پایدارتر و بهتر برای توسعه برنامه‌های خود داشته باشید. اگر هنوز با اصطلاحات داکر آشنا نشده‌اید، پیشنهاد می‌کنم مقاله مهم‌ترین اصطلاحات داکر را مطالعه کنید.

سوالات متداول

شما می‌توانید با انجام موارد زیر داکرفایل خود را بهینه کنید:

  • بسته بندی مجدد برای افزایش سرعت
  • کاهش داده‌های اضافی
  • دانلودهای موازی
  • فشرده سازی 

با انجام سه نکته زیر می‌توانید سرعت ساخت در داکرفایل را بیشتر کرده و زمان بیلد را پایین‌تر بیاورید:

  1. ایجاد تصویر میانی (Intermmediate Image)
  2. استفاده از سرویس‌های CI سریع‌تر
  3. از اجزای از پیش ساخته شده استفاده کنید.

داکرفایل یک سند متنی است که برای ایجاد و ساخت یک ایمیج کانتینر داکر استفاده می‌شود.

منابع

  • https://www.sysdig.com/learn-cloud-native/dockerfile-best-practices#5-beyond-image-building
  • https://www.qovery.com/blog/best-practices-and-tips-for-writing-a-dockerfile

به این مقاله امتیاز دهید!

میانگین امتیاز 0 / 5. تعداد رأی ها : 0

هنوز هیچ رأیی داده نشده. اولین نفر باشید!

اشتراک گذاری در تلگرام اشتراک گذاری در لینکدین اشتراک گذاری در ایکس کپی کردن لینک پست

و در ادامه بخوانید

اولین دیدگاه را اضافه کنید.

    برچسب ها

    Docker DevOps