آموزش جامع راه‌اندازی اپلیکیشن Node.js در محیط توسعه با Docker و Docker Compose

آموزش جامع راهاندازی اپلیکیشن Node.js در محیط توسعه با Docker و Docker Compose

این راهنمای جامع گامبهگام نشان میدهد چگونه یک اپلیکیشن Node.js را برای توسعه محلی کانتینریزه کنید. از ایجاد Dockerfile و .dockerignore گرفته تا تنظیم docker-compose برای live-reload با nodemon، مدیریت متغیرهای محیطی و رفع خطاها پوشش داده شده است.

تاریخ انتشار:۱۶ آذر ۱۴۰۴
زمان مطالعه:1 دقیقه

فهرست مطالب

آموزش جامع راهاندازی اپلیکیشن Node.js در محیط توسعه با Docker و Docker Compose

در این آموزش گامبهگام, یک اپلیکیشن ساده Node.js را برای محیط توسعه (development) به کمک Docker و Docker Compose کانتینریزه میکنیم؛ طوری که:

  • با تغییر کد, live-reload داشته باشید (با nodemon)
  • فقط با یک دستور پروژه را راهاندازی کنید
  • وابستگیها (node_modules) داخل کانتینر مدیریت شوند و با کد شما قاطی نشوند
  • متغیرهای محیطی را بهراحتی مدیریت کنید
  • خطاهای متداول را بشناسید و سریع رفع کنید

۱. چرا Docker برای توسعه Node.js مفید است؟

خیلی کوتاه و کاربردی:

  • همسان بودن محیط روی همه سیستمها
    فرقی نمیکند روی ویندوز, مک یا لینوکس باشید؛ با Docker همه روی یک محیط استاندارد کار میکنند.

  • حذف مشکلروی سیستم من کار میکنه!”
    نسخه Node, پکیجها, سیستمعامل, همه در Docker تعریف میشوند؛ پس تفاوت محیطها کمتر دردسر درست میکند.

  • جداسازی وابستگیها
    دیگر لازم نیست روی سیستم اصلیتان چند نسخه Node نصب کنید یا node_modulesهای مختلف داشته باشید.

  • نزدیک بودن محیط توسعه به محیط تولید
    وقتی از همان ایمیج/تنظیمات (یا نزدیک به آن) در production استفاده کنید, بروز باگهای عجیب کمتر میشود.

  • راهاندازی سریع پروژه برای اعضای جدید تیم
    فقط کافی است Docker و Docker Compose داشته باشند و یک دستور اجرا کنند.


۲. فایلهای پروژه

یک پوشه جدید بسازید, مثلا:

code
mkdir node-docker-dev
cd node-docker-dev

در این پوشه, پنج فایل زیر را ایجاد میکنیم (همه را کامل و قابل کپی در ادامه میبینید):

  • server.js
  • package.json
  • Dockerfile
  • .dockerignore
  • docker-compose.yml

در ادامه محتوا و توضیح هرکدام را میآوریم.


۲.۱. فایل server.js

یک سرور ساده Express با استفاده از متغیر محیطی PORT:

code
// server.js
const express = require('express');

const app = express();

// خواندن پورت از متغیر محیطی, در صورت نبودن, 3000
const PORT = process.env.PORT || 3000;

// یک روت ساده برای تست
app.get('/', (req, res) => {
  res.json({
    message: 'سلام از داخل کانتینر Docker! 🎉',
    env: process.env.NODE_ENV || 'development',
  });
});

// روت سلامت سرویس
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

// استارت سرور
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

(ایموجی در پاسخ HTTP ایرادی ندارد؛ قانون شما مربوط به متن مقاله است.)


۲.۲. فایل package.json

اسکریپت dev با nodemon برای live-reload:

code
{
  "name": "node-docker-dev-example",
  "version": "1.0.0",
  "description": "نمونه ساده اپلیکیشن Node.js برای توسعه در Docker با Docker Compose و nodemon",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon --legacy-watch server.js"
  },
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "express": "^4.19.2"
  },
  "devDependencies": {
    "nodemon": "^3.1.0"
  }
}

نکتهها:

  • dev از nodemon استفاده میکند تا با تغییر فایلها, سرور را خودکار ریاستارت کند.
  • فلگ --legacy-watch کمک میکند روی بعضی سیستمها (مثلا Docker روی ویندوز/WSL) نظارت روی فایلها مطمئنتر کار کند.

۲.۳. فایل Dockerfile

code
# Dockerfile
FROM node:18-alpine

# دایرکتوری کاری داخل کانتینر
WORKDIR /app

# فقط فایل‌های وابستگی را کپی می‌کنیم تا cache بهتر کار کند
COPY package*.json ./

# نصب وابستگی‌ها
RUN npm install

# کپی بقیه کدها داخل کانتینر
COPY . .

# پورت اپلیکیشن
EXPOSE 3000

# دستور پیش‌فرض (در docker-compose override می‌کنیم)
CMD [ "npm", "run", "dev" ]

در بخش ۳, همین فایل را خط به خط توضیح میدهیم.


۲.۴. فایل .dockerignore

این فایل دقیقا مثل .gitignore است اما برای Docker؛ به او میگوییم چه چیزهایی را در ایمیج کپی نکند:

code
node_modules
npm-debug.log
Dockerfile
docker-compose.yml
.dockerignore
.git
.gitignore
.env
*.log

مهمترین آیتم اینجاست:
node_modules
چون میخواهیم node_modules را داخل کانتینر بسازیم, نه از سیستم میزبان کپی کنیم.


۲.۵. فایل docker-compose.yml

فایل اصلی برای راهاندازی کانتینر در محیط توسعه:

code
version: "3.8"

services:
  app:
    build: .
    container_name: node-docker-dev-app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
      # برای اطمینان از کارکردن watch در بعضی سیستم‌ها
      - CHOKIDAR_USEPOLLING=true
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

در بخش ۴ این فایل را خط به خط و مخصوصاً قسمت volumes را توضیح میدهیم.


۳. توضیح Dockerfile خط به خط

حال همان Dockerfile را با توضیح ریز میخوانیم:

code
FROM node:18-alpine
  • استفاده از ایمیج رسمی Node نسخه ۱۸ بر پایه Alpine (سبک و سریع).
  • همهچیز روی این بیس ایمیج نصب میشود.
code
WORKDIR /app
  • دایرکتوری کاری داخل کانتینر را /app قرار میدهیم.
  • از این به بعد هر دستور (مثل RUN, COPY, CMD) نسبی به این مسیر است.
code
COPY package*.json ./
  • package.json و اگر وجود دارد package-lock.json را به /app کپی میکند.
  • این ترفند باعث میشود اگر فقط کد تغییر کند و وابستگیها ثابت باشند, Docker از cache این مرحله استفاده کند و دوباره npm install را اجرا نکند.
code
RUN npm install
  • همه وابستگیهای پروژه را نصب میکند.
  • خروجی این دستور در لایههای ایمیج ذخیره میشود.
code
COPY . .
  • بقیه فایلهای پروژه (از جمله server.js) را داخل /app کپی میکند.
  • با .dockerignore مطمئن شدیم که node_modules و فایلهای اضافی وارد ایمیج نمیشوند.
code
EXPOSE 3000
  • اعلام میکند این کانتینر روی پورت ۳۰۰۰ گوش میکند.
  • فقط جنبهی مستندسازی و کمک به ابزارها را دارد؛ خودبهخود پورت را publish نمیکند (این کار را در docker-compose انجام میدهیم).
code
CMD [ "npm", "run", "dev" ]
  • دستور پیشفرض کانتینر را مشخص میکند: اجرای اسکریپت dev (یعنی nodemon).
  • در docker-compose.yml این دستور را دوباره مشخص کردهایم (override) تا اگر بعدا CMD را عوض کردید, همچنان در توسعه مشخص باشد.

۴. توضیح docker-compose.yml خط به خط

فایل کامل:

code
version: "3.8"

services:
  app:
    build: .
    container_name: node-docker-dev-app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
      - CHOKIDAR_USEPOLLING=true
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

برویم خط به خط:

code
version: "3.8"
  • نسخه فرمت Docker Compose. نسخه ۳٫۸ روی اکثر نسخههای جدید Docker پشتیبانی میشود.
code
services:
  app:
  • services لیستی از سرویسها (کانتینرها) است.
  • ما فقط یک سرویس داریم به نام app.
code
    build: .
  • به Docker میگوید ایمیج را از روی همین پوشه (.) بسازد.
  • یعنی از همین Dockerfile استفاده کند.
code
    container_name: node-docker-dev-app
  • یک نام خوانا برای کانتینر تعیین میکند تا به جای ID طولانی, با این نام به آن رجوع کنیم.
code
    ports:
      - "3000:3000"
  • پورت ۳۰۰۰ روی میزبان را به پورت ۳۰۰۰ داخل کانتینر وصل میکند.
  • فرمت کلی: "HOST:CONTAINER"
  • نتیجه: با رفتن به http://localhost:3000 به اپ داخل کانتینر وصل میشوید.
code
    environment:
      - NODE_ENV=development
      - PORT=3000
      - CHOKIDAR_USEPOLLING=true
  • این متغیرها در process.env داخل Node در دسترس هستند.
  • NODE_ENV=development یعنی محیط توسعه.
  • PORT=3000 را در server.js میخوانیم.
  • CHOKIDAR_USEPOLLING=true برای بهتر کار کردن watch فایلها داخل برخی محیطها (مخصوصا Docker روی ویندوز/macOS).

اگر خواستید میتوانید این متغیرها را در .env بذارید و در docker-compose.yml فقط نامشان را بنویسید, ولی برای سادگی اینجا مستقیم نوشتیم.


۴.۱. توضیح مهم: volumes و جلوگیری از بازنویسی node_modules

code
    volumes:
      - .:/app
      - /app/node_modules

این دو خط مهمترین قسمت برای توسعه هستند:

۱. - .:/app

  • یک bind mount است:
    پوشهی فعلی پروژه روی سیستم شما (.) را روی /app داخل کانتینر mount میکند.
  • نتیجه:
    • هر تغییری در فایلهای کد (مثلاً server.js) روی هاست بکنید, بلافاصله در کانتینر هم دیده میشود.
    • nodemon با دیدن این تغییرها سرور را دوباره استارت میکندlive-reload.

اما یک مشکل بالقوه دارد:

  • اگر روی سیستمتان پوشه node_modules وجود داشته باشد, با این mount, آن پوشه روی /app/node_modules داخل کانتینر سایه میاندازد (overwrite میکند).
  • این باعث میشود:
    • وابستگیهایی که داخل کانتینر نصب شدند, دیگر دیده نشوند.
    • یا اگر روی هاست node_modules ندارید, آن مسیر خالی میشود و خطای عدم وجود ماژول میگیرید.

۲. - /app/node_modules

این خط دقیقا برای حل همین مشکل است.

  • این یک anonymous volume است.
  • یعنی میگوید: مسیر /app/node_modules داخل کانتینر را روی یک volume داخلی Docker نگهدار؛ نه روی پوشهی پروژه روی هاست.
  • نتیجه:
    • Docker از نتیجهی npm install داخل خودش مراقبت میکند.
    • حتی اگر کل پروژه را mount کنیم (.:/app), پوشش /app/node_modules با این volume داخلی محافظت میشود و دچار بازنویسی از طرف هاست نمیشود.

به زبان ساده:

  • خط اول: «کد من را از سیستم به کانتینر بده, تا live-reload داشته باشم».
  • خط دوم: «ولی لطفاً node_modules را از هاست نگیر؛ خودت داخل کانتینر نگهشان دار».

بدون خط دوم, خیلی وقتها به خطاهای Cannot find module, permission denied یا رفتارهای عجیب با وابستگیها میخورید.


code
    command: npm run dev
  • دستور اجرا زمانی که کانتینر برای این سرویس بالا میآید.
  • CMD داخل Dockerfile را override میکند (در اینجا البته یکی هستند, اما اگر در Dockerfile عوضش کنید, اینجا دست بالا را دارد).
  • npm run dev همان nodemon است که روی فایلهای پروژه watch میکند و با هر تغییری سرور را ریاستارت میکند.

۵. اجرای پروژه (فقط یک دستور)

تا اینجا همه فایلها را ساختهاید. حالا:

۱. مطمئن شوید داخل پوشهی پروژه هستید (جایی که docker-compose.yml قرار دارد):

code
cd path/to/node-docker-dev

۲. دستور زیر را اجرا کنید:

code
docker-compose up --build

این کار:

  • ایمیج را (بر اساس Dockerfile) میسازد یا بهروزرسانی میکند.
  • کانتینر را بالا میآورد.
  • لاگها را در همان ترمینال نشان میدهد.

پس از آماده شدن, در مرورگر بروید به:

  • http://localhost:3000/پیام JSON از سرور
  • http://localhost:3000/healthوضعیت سلامت

حالا یک تغییر کوچک در server.js بدهید (مثلا متن پیام را عوض کنید) و فایل را ذخیره کنید؛ باید ببینید که:

  • در ترمینال, nodemon تشخیص میدهد فایل عوض شده و سرور را ریاستارت میکند.
  • با رفرش صفحه مرورگر, نتیجه جدید را میبینید.

۶. دستورات مفید Docker Compose در توسعه

چند دستور پایه که در طول توسعه زیاد لازمتان میشود:

۶.۱. دیدن لاگها

اگر کانتینر را در پسزمینه اجرا کردهاید (با -d) یا ترمینال قبلی را بستهاید:

code
docker-compose logs -f
  • -f یعنی لاگها را بهصورت زنده (follow) نشان بده.

فقط لاگ یک سرویس خاص (مثلا app):

code
docker-compose logs -f app

۶.۲. ورود به داخل کانتینر (exec)

برای اجرای دستوری داخل کانتینر در حال اجرا (مثلا چک کردن نسخه Node, اجرای ls و غیره):

code
docker-compose exec app sh
  • این دستور یک شل (sh) داخل کانتینر باز میکند.
  • حالا میتوانید مثلا بزنید:
code
node -v
npm ls
ls

برای خروج: exit


۶.۳. خاموش کردن و پاک کردن کانتینرها

برای متوقف کردن سرویسها:

code
docker-compose down

اگر میخواهید همهی volumeهای ناشناس (از جمله /app/node_modules) را هم پاک کنید:

code
docker-compose down -v

این کار مفید است وقتی:

  • وابستگیها بههم ریختهاند
  • میخواهید از صفر npm install در کانتینر انجام شود

۷. رفع خطاهای متداول

در کار با Docker + Node + nodemon چند خطا خیلی رایج است. این بخش را نگه دارید که بعداً سریع برگردید و چک کنید.


۷.۱. مشکل: live-reload کار نمیکند

علائم:

  • nodemon اجرا میشود, اما وقتی کد را تغییر میدهید, سرور ریاستارت نمیشود.
  • یا فقط بعضی وقتها حس میکنید تغییرها اعمال نمیشوند.

چکلیست رفع مشکل:

۱. آیا volume اصلی را درست ست کردهاید؟

در docker-compose.yml باید این را داشته باشید:

code
volumes:
  - .:/app
  - /app/node_modules

اگر .:/app را حذف کرده باشید, کد روی هاست به کانتینر sync نمیشود و nodemon تغییری نمیبیند.

۲. آیا اسکریپت dev درست است؟

در package.json:

code
"scripts": {
  "dev": "nodemon --legacy-watch server.js"
}

اگر به اشتباه start را اجرا کنید, live-reload ندارید؛ چون node خالی است, نه nodemon.

۳. روی ویندوز / WSL / macOS هستید؟

بعضی وقتها مکانیزم watch فایلها در این محیطها داخل Docker خوب کار نمیکند. راهحل:

  • ما قبلا این را در docker-compose.yml گذاشتیم:
code
environment:
  - CHOKIDAR_USEPOLLING=true
  • و در package.json از --legacy-watch استفاده کردیم.

اگر هنوز مشکل دارید, یکبار کانتینر را از نو بسازید:

code
docker-compose down -v
docker-compose up --build

۷.۲. مشکل: خطای پورت اشغال شده (EADDRINUSE)

علائم:

  • در لاگها میبینید:
code
Error: listen EADDRINUSE: address already in use :::3000

علت:

  • یک سرویس دیگر روی سیستم شما (احتمالا نسخه لوکال Node بدون Docker, یا سرویس دیگری) در حال استفاده از پورت ۳۰۰۰ است.

راهحلساده:

۱. یا آن سرویس را ببندید (اگر مثلا قبلاً npm start محلی اجرا کردهاید آن را متوقف کنید).

۲. یا پورت را عوض کنید. مثلا:

  • در server.js:
code
const PORT = process.env.PORT || 4000;
  • در docker-compose.yml:
code
ports:
  - "4000:4000"
environment:
  - PORT=4000

بعد:

code
docker-compose up --build

و به http://localhost:4000 بروید.


۷.۳. مشکل: permission denied مخصوصاً روی node_modules

علائم:

  • در لاگها یا ترمینال داخل کانتینر ارورهایی مثل زیر میبینید:
code
EACCES: permission denied, mkdir '/app/node_modules'
Error: EACCES: permission denied, access '/app/node_modules'

علتهای رایج:

  • node_modules روی هاست با مالک/سطح دسترسیای ساخته شده که با کاربر داخل کانتینر سازگار نیست.
  • node_modules روی هاست mount شده و با /app/node_modules قاطی شده است.

راهحل پیشنهادی (سریع و ساده):

۱. مطمئن شوید در .dockerignore, node_modules را دارید (که داریم).

۲. volumeمربوط به کانتینر را پاک کنید و از صفر بسازید:

code
docker-compose down -v
rm -rf node_modules
docker-compose up --build
  • down -v همه volumeها (از جمله /app/node_modules) را پاک میکند.
  • بعد, Docker دوباره npm install را در محیط خودش و با دسترسیهای درست اجرا میکند.

۳. اگر در محیط لینوکس هستید و Docker را با sudo باید اجرا کنید, ترجیحاً کاربر خودتان را در گروه docker قرار دهید تا نیاز به sudo نباشد. ولی برای شروع, اجرای دستورات با sudo هم قابل قبول است:

code
sudo docker-compose up --build

جمعبندی

در این راهنما:

  • یک اپلیکیشن ساده Node.js با Express ساختیم (server.js + package.json).
  • یک Dockerfile سبک با Node 18 Alpine نوشتیم.
  • با .dockerignore از ورود فایلهای اضافی (بهخصوص node_modules) به ایمیج جلوگیری کردیم.
  • با docker-compose.yml:
    • پورتها را map کردیم,
    • متغیرهای محیطی را تنظیم کردیم,
    • با volumes کاری کردیم که:
      • کد از هاست به کانتینر sync شود (برای live-reload)
      • ولی node_modules داخل کانتینر بماند و بازنویسی نشود.
  • یاد گرفتیم فقط با یک دستور:
code
docker-compose up --build

پروژه را در محیط توسعه راهاندازی کنیم.

  • و چند دستور مهم (logs, exec, down) و خطاهای متداول (live-reload, پورت اشغال شده, permission denied) را مرور کردیم.

از این نقطه به بعد, میتوانید:

  • روتهای بیشتری اضافه کنید,
  • دیتابیس (مثل PostgreSQL یا MongoDB) را به عنوان سرویس جدید در همان docker-compose.yml تعریف کنید,
  • و عملاً یک محیط توسعه کامل و ایزوله روی Docker داشته باشید.

دسته‌بندی‌ها:

داکر

برچسب‌ها:

#DevOps#Docker Compose#Node.js#آموزش داکر#داکر#کانتینرسازی#نود جی اس

بیشتر مطالعه کنید

آموزش جامع راهاندازی اپلیکیشن Node.js در محیط توسعه با Docker و Docker Compose

این راهنمای جامع گامبهگام نشان میدهد چگونه یک اپلیکیشن Node.js را برای توسعه محلی کانتینریزه کنید. از ایجاد Dockerfile و .dockerignore گرفته تا تنظیم docker-compose برای live-reload با nodemon، مدیریت متغیرهای محیطی و رفع خطاها پوشش داده شده است.

۱۶ آذر ۱۴۰۴
1 دقیقه

راهنمای کامل فرایند OCEAN در پرامپتنویسی برای دریافت بهترین خروجی از مدلهای زبانی

این مطلب یک راهنمای جامع برای استفاده از فرایند OCEAN در پرامپتنویسی است؛ روشی پنجمرحلهای شامل هدفگذاری، ارائه زمینه، استفاده از مثال، ارزیابی خروجی و مذاکره برای بهبود نتیجه. این فرآیند کمک میکند از مدلهای زبانی مانند ChatGPT بهترین خروجی را دریافت کنید. در پایان نیز دو ابزار ایرانیپلتفرم چت «اکوان» و گیتوی API خاتشبهعنوان گزینههای مناسب برای اجرای این روش معرفی شدهاند.

۲۳ آبان ۱۴۰۴
3 دقیقه

Grok 4 Fast با کانتکست ۲ میلیونی؛ بررسی واقعی و روش استفاده از طریق خاتش

مدل Grok 4 Fast از xAI با پنجرهی کانتکست ۲ میلیون توکنی معرفی شده است. در این مقاله بررسی میکنیم آیا این پیشرفت واقعاً کاربردی است یا صرفاً عددی تبلیغاتی، و در ادامه روش استفاده از این مدل از طریق API خاتش با پرداخت ریالی و پلتفرم Akvan.chat را توضیح میدهیم.

۲۰ آبان ۱۴۰۴
2 دقیقه

تفاوت زیرساخت هایبرید و سرور ابریکدام گزینه برای کسبوکار شما بهتر است؟

در این مقاله از بلاگ خاتش، تفاوت میان زیرساخت ابری (Cloud Infrastructure) و زیرساخت هایبرید (Hybrid Infrastructure) را بهصورت شفاف و کاربردی بررسی میکنیم. میخوانید که هر مدل دقیقاً چیست، در چه شرایطی مناسبتر است، و چرا بسیاری از سازمانها ترکیبی از هر دو را انتخاب میکنند.

۱۹ آبان ۱۴۰۴
4 دقیقه
آموزش جامع Node.js و Docker برای توسعه (Docker Compose)