آموزش ساخت ایمیج داکر برای اپلیکیشن‌های Node.js


اگر شما هم توسعه‌دهنده Node.js هستید، احتمالاً با چالش‌هایی مانند “روی سیستم من کار می‌کنه!” (it works on my machine)  روبه‌رو شده‌اید. این جمله، خلاصه‌ای از مشکلات محیطی است که هر توسعه‌دهنده‌ای با آن دست‌و‌پنجه نرم می‌کند. راه‌حل این مشکل، استفاده از داکر است. با استفاده از داکر، می‌توانید مطمئن شوید که برنامه شما در هر محیطی، دقیقا به همان شکلی که انتظار دارید، اجرا می‌شود. این مقاله یک راهنمای کامل برای ساخت ایمیج داکر برای اپلیکیشن‌های Node.js  است و به شما کمک می‌کند تا فرآیند استقرار پروژه‌های خود را به شکلی ساده و بهینه مدیریت کنید. با ما همراه باشید.

چرا باید از داکر برای Node.js استفاده کنیم؟

بیایید با این پرسش شروع کنیم که داکر برای Node.js چیست؟ داکر یک پلتفرم متن‌باز است که به شما امکان می‌دهد تا برنامه‌های خود و تمام وابستگی‌های آن‌ها را در یک واحد قابل حمل و مستقل به نام کانتینر (Container)  قرار دهید. استفاده از داکر برای پروژه‌هایNode.js  مزایای زیادی دارد که به شما کمک می‌کند تا با چالش‌های رایج در توسعه نرم‌افزار مقابله کنید. مزایای اصلی استفاده از داکر برای توسعه‌دهندگان Node.js عبارتند از:

  • یکپارچه‌سازی محیط: همه اعضای یک تیم می‌توانند با یک محیط یکسان کار کنند. این کار از بروز مشکلاتی که به دلیل تفاوت در سیستم‌عامل، نسخه‌های نرم‌افزار یا وابستگی‌ها ایجاد می‌شوند، جلوگیری می‌کند.
  • افزایش امنیت: داکر اپلیکیشن‌ها را در کانتینرها ایزوله می‌کند. هر کانتینر یک محیط مستقل است و تغییرات در آن بر روی سایر کانتینرها تأثیری نمی‌گذارد. این جداسازی به افزایش امنیت کلی سیستم کمک می‌کند.
  • اتوماسیون فرآیند استقرار: داکر به شما کمک می‌کند تا فرآیند استقرار (Deployment) را خودکار کنید و به راحتی با ابزارهای CI/CD (Continuous Integration/Continuous Deployment) یکپارچه شوید. این ویژگی برای تیم‌های بزرگ و پروژه‌های پیچیده که نیاز به استقرار سریع و مداوم دارند، بسیار حیاتی است.
  • قابلیت حمل بالا: با داکر می‌توانید از ساخت ایمیج سفارشی برای اپلیکیشن‌های خود استفاده کنید. این ایمیج‌ها شامل تمام چیزهایی هستند که برای اجرای برنامه لازم است و به راحتی می‌توان آن‌ها را بین سرورهای مختلف جابجا کرد، بدون این که نگران تنظیمات محیطی باشید.

این ویژگی‌ها داکر را به یک ابزار ضروری برای هر توسعه‌دهنده Node.js تبدیل کرده است.

آماده‌سازی پروژه برای داکرسازی

قبل از این که به سراغ نوشتن Dockerfile برویم، ابتدا باید پروژه Node.js خود را برای فرآیند داکرسازی یا داکرایز آماده کنیم. این مرحله شامل ساخت یک اپلیکیشن ساده و ایجاد یک فایل مهم به نام dockerignore.  است. Dockerize کردن پروژه Node.js با یک ساختار تمیز و بهینه شروع می‌شود.

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

/my-node-app

├── node_modules/

├── package.json

├── package-lock.json

├── server.js

└── .dockerignore

فایل server.js  ما یک وب‌سرور ساده راه‌اندازی می‌کند:

JavaScript

// server.js

const express = require(‘express’);

const app = express();

const PORT = 8080;

const HOST = ‘0.0.0.0’;

app.get(‘/’, (req, res) => {

  res.send(‘Hello world from a Docker container!’);

});

app.listen(PORT, HOST, () => {

  console.log(Server running on http://${HOST}:${PORT});

});

و فایل package.json  وابستگی‌های پروژه را مشخص می‌کند:

JSON

// package.json

{

  “name”: “my-node-app”,

  “version”: “1.0.0”,

  “description”: “A simple Node.js application for Docker”,

  “main”: “server.js”,

  “scripts”: {

    “start”: “node server.js”

  },

  “dependencies”: {

    “express”: “^4.18.2”

  }

}

اهمیت فایل  dockerignore

یکی از مهم‌ترین قدم‌ها در آموزش داکرسازی برنامه Node.js، ساخت فایل dockerignore.  است. این فایل دقیقاً مانند gitignore.  عمل می‌کند، اما برای داکر. هر فایل یا پوشه‌ای که نامش در این فایل ذکر شود، هنگام ساخت Image (ایمیج) توسط داکر نادیده گرفته می‌شود و به داخل ایمیج کپی نمی‌شود.

اما چرا این کار اهمیت دارد؟

  1. کاهش حجم ایمیج: با نادیده گرفتن فایل‌های غیرضروری مانند پوشه node_modules  (که قرار است داخل کانتینر نصب شود)، لاگ‌ها، یا فایل‌های مربوط به سیستم‌عامل، حجم ایمیج نهایی به شدت کاهش پیدا می‌کند.
  2. افزایش سرعت ساخت (Build): کپی کردن فایل‌های کمتر به معنای زمان ساخت کوتاه‌تر است.
  3. جلوگیری از بازنویسی ناخواسته: ما می‌خواهیم npm install  را داخل Container  (کانتینر) اجرا کنیم. اگر node_modules  سیستم خودمان را به داخل ایمیج کپی کنیم، ممکن است با وابستگی‌های کامپایل‌شده برای سیستم‌عامل میزبان (مثلاً macOS یا Windows) تداخل پیدا کند و در محیط لینوکسی کانتینر به درستی کار نکند.
  4. امنیت: با این کار از کپی شدن فایل‌های حساس مانند env.  یا کلیدهای SSH به داخل ایمیج جلوگیری می‌کنیم.

یک نمونه فایل .dockerignore  برای پروژه‌های Node.js به این شکل است:

# .dockerignore

node_modules

npm-debug.log

Dockerfile

.dockerignore

.git

.gitignore

حالا که پروژه آماده است، بیایید دو مفهوم کلیدی را مرور کنیم. Image  (ایمیج) یک بسته قابل اجرا، سبک و مستقل است که شامل همه چیز برای اجرای یک نرم‌افزار می‌شود: کد، زمان اجرا (runtime)، ابزارهای سیستمی، کتابخانه‌ها و تنظیمات. Container (کانتینر) یک نمونه‌ی در حال اجرا (running instance) از یک ایمیج است. شما می‌توانید از یک ایمیج، چندین کانتینر مستقل و ایزوله ایجاد کنید.

ساخت Dockerfile پایه برای Node.js

فایل Dockerfile، پایه و اساس فرآیند داکرسازی است. Dockerfile یک فایل متنی ساده است که مجموعه‌ای از دستورالعمل‌ها را برای ساختن ایمیج داکر شما در خود جای داده است. در این بخش، به این سوال رایج شما یعنی “چگونه یک Dockerfile  بسازم؟ “پاسخ می‌دهیم و یک Dockerfile  برای اپلیکیشن Node.js با هم می‌سازیم.

هر خط در این فایل یک لایه (Layer) جدید در ایمیج ایجاد می‌کند. داکر از کش لایه‌ها استفاده می‌کند تا در بیلدهای بعدی، فقط لایه‌هایی که تغییر کرده‌اند را دوباره بسازد. این ویژگی، سرعت فرآیند توسعه را به‌شدت بالا می‌برد.

در ادامه، یک Dockerfile  پایه برای اپلیکیشن Node.js که بالاتر ساختیم، آورده شده است. هر دستور به دقت توضیح داده شده است.

Dockerfile

# Dockerfile پایه برای Node.js

# 1. انتخاب ایمیج پایه (Base Image)

# از یک نسخه رسمی و LTS (پشتیبانی طولانی مدت) از نود استفاده می‌کنیم.

FROM node:18-slim

# 2. تعیین دایرکتوری کاری داخل کانتینر

# تمام دستورات بعدی در این مسیر اجرا خواهند شد.

WORKDIR /usr/src/app

# 3. کپی کردن فایل‌های وابستگی

# ابتدا فقط package.json و package-lock.json را کپی می‌کنیم.

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

# تا زمانی که این فایل‌ها تغییر نکنند، داکر در بیلدهای بعدی npm install را دوباره اجرا نمی‌کند.

COPY package*.json ./

# 4. نصب وابستگی‌های پروژه

# از npm ci برای نصب دقیق وابستگی‌ها از روی package-lock.json استفاده می‌کنیم که برای محیط‌های CI/CD بهتر است.

RUN npm ci –only=production

# 5. کپی کردن سورس کد پروژه

# حالا که وابستگی‌ها نصب شده، بقیه کدهای اپلیکیشن را کپی می‌کنیم.

COPY . .

# 6. مشخص کردن پورت (Port)

# به داکر اطلاع می‌دهیم که اپلیکیشن ما روی چه پورتی اجرا خواهد شد.

EXPOSE 8080

# 7. تعریف دستور اجرای اپلیکیشن (CMD)

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

CMD [ “node”, “server.js” ]

بررسی دستورهای  Dockerfile

در ادامه با مهم‌ترین دستورهای Dockerfile که در نمونه بالا استفاده شد، بیشتر آشنا می‌شویم:

  • FROM: این دستور همیشه اولین دستور در یک Dockerfile  (داکرفایل) است و base image  (ایمیج پایه) را مشخص می‌کند. ما از node:18-slim  استفاده کرده‌ایم که یک نسخه بهینه و کم‌حجم‌تر از ایمیج رسمی Node.js  است.
  • WORKDIR: دایرکتوری کاری را داخل کانتینر تنظیم می‌کند. اگر این دایرکتوری وجود نداشته باشد، داکر آن را می‌سازد.
  • COPY: فایل‌ها را از سیستم میزبان (Host) به سیستم فایل کانتینر کپی می‌کند. ترفند کپی کردن package*.json  به صورت جداگانه و قبل از بقیه کدها، یک بهینه‌سازی مهم برای استفاده از کش داکر است.
  • RUN: یک دستور را در پوسته‌ی (shell) کانتینر اجرا می‌کند. ما از آن برای اجرای npm install  ( یا بهتر از آن npm ci) استفاده می‌کنیم تا وابستگی‌ها را نصب کنیم.
  • EXPOSE: این دستور به داکر اطلاع می‌دهد که کانتینر روی کدام port  به شبکه گوش می‌دهد. این دستور به تنهایی پورت را باز نمی‌کند، بلکه صرفاً جنبه مستندسازی دارد و به ابزارهایی که با کانتینر در ارتباط هستند، کمک می‌کند.
  • CMD: دستور پیش‌فرض برای اجرای کانتینر را فراهم می‌کند. این دستور زمانی اجرا می‌شود که کانتینر استارت شود. تنها یک دستور CMD  در یک Dockerfile می‌تواند وجود داشته باشد.

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

بهینه‌سازی Dockerfile برای Node.js (استفاده از Multi-stage builds)

ایمیجی که در مرحله قبل ساختیم کار می‌کند، اما حجم آن نسبتاً زیاد است و شامل تمام devDependencies  و ابزارهای مورد نیاز برای بیلد است که در محیط پروداکشن به آن‌ها نیازی نیست. برای بهینه‌سازی Dockerfile برای اپلیکیشن Node.js  و رسیدن به بهترین Dockerfile برای Node.js، از تکنیکی به نام multi-stage build  (ساخت چندمرحله‌ای) استفاده می‌کنیم.

اما چگونه حجم ایمیج داکر را کاهش دهیم؟ ساخت چندمرحله‌ای، پاسخ این سوال است. در این روش، ما از چندین دستور FROM  در یک Dockerfile استفاده می‌کنیم. هر FROM  یک مرحله (stage) جدید را شروع می‌کند. ما می‌توانیم در یک مرحله‌ی اولیه (که به آن builder  یا development image  می‌گوییم) تمام وابستگی‌ها را نصب و کدهایمان را آماده کنیم و سپس در مرحله‌ی نهایی، فقط و فقط فایل‌های ضروری  (artifactها) را از مرحله‌ی اول به یک ایمیج پایه‌ی بسیار سبک (مانند alpine) کپی کنیم تا یک production image  تمیز و کم‌حجم داشته باشیم.

از مهم‌ترین مزایای  Multi-stage buildsمی‌توان به موارد زیر اشاره کرد:

  • حجم بسیار کمتر ایمیج نهایی: با حذف تمام فایل‌های غیرضروری، ایمیج نهایی ممکن است تا 90% کوچک‌تر شود.
  • افزایش امنیت: سطح حمله (attack surface) کاهش می‌یابد، زیرا ابزارهای بیلد، کامپایلرها و وابستگی‌های توسعه در ایمیج نهایی وجود ندارند.
  • Dockerfile تمیزتر: تمام منطق بیلد در یک فایل واحد باقی می‌ماند و نیازی به اسکریپت‌های جداگانه نیست.

در ادامه، Dockerfile  بهینه‌شده با این تکنیک را مشاهده می‌کنید:

Dockerfile

# Dockerfile بهینه‌شده با Multi-stage Build

# —– مرحله اول: Builder —–

# این مرحله برای نصب وابستگی‌ها و آماده‌سازی کد استفاده می‌شود.

FROM node:18-slim AS builder

# تنظیم دایرکتوری کاری

WORKDIR /usr/src/app

# کپی کردن فایل‌های وابستگی

COPY package*.json ./

# نصب تمام وابستگی‌ها، از جمله devDependencies

RUN npm install

# کپی کردن سورس کد

COPY . .

# (اختیاری) اگر مرحله بیلد دارید، اینجا اجرا کنید

# RUN npm run build

# —– مرحله دوم: Production —–

# این مرحله نهایی است و ایمیج پروداکشن را می‌سازد.

# از یک ایمیج پایه بسیار سبک استفاده می‌کنیم.

FROM node:18-alpine

# تنظیم دایرکتوری کاری

WORKDIR /usr/src/app

# کپی کردن package.json و package-lock.json از مرحله builder

COPY –from=builder /usr/src/app/package*.json ./

# نصب فقط وابستگی‌های پروداکشن

# استفاده از npm ci باعث می‌شود فرآیند قابل تکرار و سریع‌تر باشد.

RUN npm ci –only=production

# کپی کردن سورس کد از مرحله builder

COPY –from=builder /usr/src/app .

# ایجاد یک کاربر غیر روت برای افزایش امنیت

# اجرای کانتینر با کاربر root یک ریسک امنیتی است.

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

USER appuser

# مشخص کردن پورت

EXPOSE 8080

# دستور اجرای اپلیکیشن

CMD [ “node”, “server.js” ]

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

  1. دو دستور FROM: اولین FROM  مرحله builder  را شروع می‌کند و با AS builder  نام‌گذاری شده است. دومین FROM  مرحله نهایی را با ایمیج پایه سبک alpine  آغاز می‌کند. ایمیج‌های Alpine Linux به دلیل حجم بسیار کم خود (حدود 5 مگابایت) برای ایمیج‌های پروداکشن فوق‌العاده هستند.
  2. دستور COPY –from=builder: این دستور جادوی اصلی multi-stage build  است. ما به داکر می‌گوییم که فایل‌ها را نه از سیستم میزبان، بلکه از مرحله builder  که قبلاً ساختیم، کپی کند. به این ترتیب، فقط node_modules  مربوط به پروداکشن و سورس کد تمیز به ایمیج نهایی منتقل می‌شوند.
  3. کاربر غیر روت (Non-root user): یکی از مهم‌ترین نکات امنیتی در مدیریت فایل‌های ایمیج و کانتینرها، اجرا نکردن پروسه‌ها با کاربر root  است. ما یک کاربر و گروه جدید به نام appuser  و appgroup  ایجاد می‌کنیم و با دستور USER appuser  به داکر می‌گوییم که دستور CMD  را با این کاربر اجرا کند. این کار ریسک‌های امنیتی را در صورت نفوذ به اپلیکیشن به شدت کاهش می‌دهد.

با استفاده از این روش، شما به یک بهینه‌سازی کانفیگ عالی دست پیدا کرده‌اید که نتیجه آن یک ایمیج کوچک، سریع و امن است.

دستورات کلیدی داکر برای ایمیج Node.js

پس از نوشتن Dockerfile، باید با چند دستور اصلی داکر برای ساخت، اجرا و مدیریت ایمیج و کانتینر خود آشنا شوید. در ادامه، مهم‌ترین دستورات داکر برای Node.js را بررسی می‌کنیم. این دستورات را باید در ترمینال یا خط فرمان خود اجرا کنید.

  1. ساخت ایمیج  (docker build)

این دستور، Dockerfile شما را می‌خواند و بر اساس آن یک ایمیج می‌سازد.

Bash

docker build -t my-node-app .

  • docker build: دستور اصلی برای ساخت ایمیج.
  • -t my-node-app: فلگ-t  (مخفف tag) به شما اجازه می‌دهد یک نام و تگ برای ایمیج خود انتخاب کنید. ما نام آن راmy-node-app  گذاشتیم.
  • .: این نقطه به داکر می‌گوید که Dockerfile را در دایرکتوری فعلی پیدا کند.

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

  1. مشاهده لیست ایمیج‌ها  (docker images)

برای اینکه ببینید چه ایمیج‌هایی روی سیستم شما وجود دارد، از این دستور استفاده کنید:

Bash

docker images

در خروجی این دستور باید ایمیج my-node-app  را که به‌تازگی ساخته‌اید، مشاهده کنید.

  1. اجرای کانتینر از روی ایمیج  (docker run)

حالا که ایمیج را داریم، می‌توانیم یک کانتینر از روی آن اجرا کنیم.

Bash

docker run -p 49160:8080 -d my-node-app

  • docker run: دستور اصلی برای اجرای کانتینر.
  • -p 49160:8080: فلگ -p  (مخفف publish) برای نگاشت (map) پورت‌ها استفاده می‌شود. این دستور پورت 49160  روی سیستم میزبان (Host) را به پورت 8080  داخل کانتینر (که در Dockerfile با EXPOSE مشخص کردیم) متصل می‌کند. بنابراین، شما می‌توانید با مراجعه به http://localhost:49160  به اپلیکیشن خود دسترسی پیدا کنید.
  • -d: فلگ -d  (مخفف detach) کانتینر را در پس‌زمینه (background) اجرا می‌کند و ID کانتینر را به شما برمی‌گرداند.
  • my-node-app: نام ایمیجی که می‌خواهیم از روی آن کانتینر را اجرا کنیم.
  1. مشاهده کانتینرهای در حال اجرا  (docker ps)

برای دیدن لیست کانتینرهایی که در حال حاضر فعال هستند، از این دستور استفاده کنید:

Bash

docker ps

خروجی این دستور اطلاعات مفیدی مانند ID کانتینر، نام ایمیج، پورت‌های نگاشت‌شده و وضعیت آن را به شما نشان می‌دهد.

  1. مشاهده لاگ‌های کانتینر  (docker logs)

اگر می‌خواهید خروجی استاندارد (stdout) اپلیکیشن خود را ببینید (مثلاً پیام console.log  که در server.js  داشتیم)، از این دستور استفاده کنید:

Bash

docker logs <container_id>

کافی است <container_id>  را با ID کانتینری جایگزین کنید که از دستور docker ps  به دست آوردید.

  1. متوقف کردن کانتینر  (docker stop)

برای متوقف کردن یک کانتینر در حال اجرا، از این دستور استفاده کنید:

Bash

docker stop <container_id>

این دستورات، ابزارهای اصلی شما برای کار روزمره با داکر هستند و تسلط بر آن‌ها برای هر توسعه‌دهنده‌ی Node.js ضروری است.

نتیجه‌گیری

در این مقاله جامع، ما یک سفر کامل را از صفر تا صد برای داکریزه کردن یک اپلیکیشن Node.js طی کردیم. دیدیم که چگونه با آماده‌سازی پروژه و استفاده هوشمندانه از فایلdockerignore.  شروع کنیم، یک Dockerfile  پایه بسازیم و سپس با استفاده از تکنیک قدرتمند multi-stage builds  و بهترین شیوه‌های امنیتی، آن را به یک ایمیج پروداکشن بهینه، سبک و امن تبدیل کنیم.

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

گام بعدی در این مسیر، یادگیری ابزارهایی مانند Docker Compose  (داکر کامپوز) است که به شما اجازه می‌دهد چندین کانتینر مرتبط (مانند اپلیکیشن  Node.js، دیتابیس Redis و پایگاه داده PostgreSQL) را به سادگی و به صورت هماهنگ مدیریت و اجرا کنید.

امیدواریم این راهنما برای شما مفید بوده باشد. تجربیات یا سوالات خود را در مورد داکرسازی پروژه‌های Node.js در بخش نظرات با ما به اشتراک بگذارید!

بدون دیدگاه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *