اگر شما هم توسعهدهنده 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 (ایمیج) توسط داکر نادیده گرفته میشود و به داخل ایمیج کپی نمیشود.
اما چرا این کار اهمیت دارد؟
- کاهش حجم ایمیج: با نادیده گرفتن فایلهای غیرضروری مانند پوشه node_modules (که قرار است داخل کانتینر نصب شود)، لاگها، یا فایلهای مربوط به سیستمعامل، حجم ایمیج نهایی به شدت کاهش پیدا میکند.
- افزایش سرعت ساخت (Build): کپی کردن فایلهای کمتر به معنای زمان ساخت کوتاهتر است.
- جلوگیری از بازنویسی ناخواسته: ما میخواهیم npm install را داخل Container (کانتینر) اجرا کنیم. اگر node_modules سیستم خودمان را به داخل ایمیج کپی کنیم، ممکن است با وابستگیهای کامپایلشده برای سیستمعامل میزبان (مثلاً macOS یا Windows) تداخل پیدا کند و در محیط لینوکسی کانتینر به درستی کار نکند.
- امنیت: با این کار از کپی شدن فایلهای حساس مانند 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” ]
تفاوتهای کلیدی در نسخه بهینهشده
- دو دستور FROM: اولین FROM مرحله builder را شروع میکند و با AS builder نامگذاری شده است. دومین FROM مرحله نهایی را با ایمیج پایه سبک alpine آغاز میکند. ایمیجهای Alpine Linux به دلیل حجم بسیار کم خود (حدود 5 مگابایت) برای ایمیجهای پروداکشن فوقالعاده هستند.
- دستور COPY –from=builder: این دستور جادوی اصلی multi-stage build است. ما به داکر میگوییم که فایلها را نه از سیستم میزبان، بلکه از مرحله builder که قبلاً ساختیم، کپی کند. به این ترتیب، فقط node_modules مربوط به پروداکشن و سورس کد تمیز به ایمیج نهایی منتقل میشوند.
- کاربر غیر روت (Non-root user): یکی از مهمترین نکات امنیتی در مدیریت فایلهای ایمیج و کانتینرها، اجرا نکردن پروسهها با کاربر root است. ما یک کاربر و گروه جدید به نام appuser و appgroup ایجاد میکنیم و با دستور USER appuser به داکر میگوییم که دستور CMD را با این کاربر اجرا کند. این کار ریسکهای امنیتی را در صورت نفوذ به اپلیکیشن به شدت کاهش میدهد.
با استفاده از این روش، شما به یک بهینهسازی کانفیگ عالی دست پیدا کردهاید که نتیجه آن یک ایمیج کوچک، سریع و امن است.
دستورات کلیدی داکر برای ایمیج Node.js
پس از نوشتن Dockerfile، باید با چند دستور اصلی داکر برای ساخت، اجرا و مدیریت ایمیج و کانتینر خود آشنا شوید. در ادامه، مهمترین دستورات داکر برای Node.js را بررسی میکنیم. این دستورات را باید در ترمینال یا خط فرمان خود اجرا کنید.
ساخت ایمیج (docker build)
این دستور، Dockerfile شما را میخواند و بر اساس آن یک ایمیج میسازد.
Bash
docker build -t my-node-app .
- docker build: دستور اصلی برای ساخت ایمیج.
- -t my-node-app: فلگ-t (مخفف tag) به شما اجازه میدهد یک نام و تگ برای ایمیج خود انتخاب کنید. ما نام آن راmy-node-app گذاشتیم.
- .: این نقطه به داکر میگوید که Dockerfile را در دایرکتوری فعلی پیدا کند.
پس از اجرای این دستور، داکر مراحل تعریفشده در Dockerfile را یکی پس از دیگری اجرا میکند و در نهایت ایمیج شما آماده میشود.
مشاهده لیست ایمیجها (docker images)
برای اینکه ببینید چه ایمیجهایی روی سیستم شما وجود دارد، از این دستور استفاده کنید:
Bash
docker images
در خروجی این دستور باید ایمیج my-node-app را که بهتازگی ساختهاید، مشاهده کنید.
اجرای کانتینر از روی ایمیج (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: نام ایمیجی که میخواهیم از روی آن کانتینر را اجرا کنیم.
مشاهده کانتینرهای در حال اجرا (docker ps)
برای دیدن لیست کانتینرهایی که در حال حاضر فعال هستند، از این دستور استفاده کنید:
Bash
docker ps
خروجی این دستور اطلاعات مفیدی مانند ID کانتینر، نام ایمیج، پورتهای نگاشتشده و وضعیت آن را به شما نشان میدهد.
مشاهده لاگهای کانتینر (docker logs)
اگر میخواهید خروجی استاندارد (stdout) اپلیکیشن خود را ببینید (مثلاً پیام console.log که در server.js داشتیم)، از این دستور استفاده کنید:
Bash
docker logs <container_id>
کافی است <container_id> را با ID کانتینری جایگزین کنید که از دستور docker ps به دست آوردید.
متوقف کردن کانتینر (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 در بخش نظرات با ما به اشتراک بگذارید!
بدون دیدگاه