آموزش ساخت و دیپلوی اپلیکیشن Node.js با Docker (گامبهگام)
در این آموزش یک اپلیکیشن ساده Node.js را از صفر میسازیم، آن را داخل یک کانتینر Docker قرار میدهیم، با Docker Compose سرویسهای جانبیاش را بالا میآوریم و در نهایت ایمیج را روی Docker Hub منتشر میکنیم تا هر زمان لازم بود بتوانیم بهسادگی آن را دوباره اجرا و اسکیل کنیم.
این مطلب بر اساس آموزش معروف DigitalOcean نوشته شده، اما با توضیحات و مثالهای جمعوجور و مناسب برای برنامهنویسهای فارسیزبان.
چرا Docker برای Node.js مهم است؟
برای برنامهنویسها، بیشترین دردسر معمولاً از اختلاف محیطها میآید:
- روی سیستم من کار میکند، روی سرور نه
- نسخه Node.js فرق دارد
- یکجا Redis هست، یکجا نیست
Docker این مشکل را با کانتینرسازی حل میکند:
- همهچیز (کد، پکیجها، کانفیگها، نسخه Node.js و …) داخل یک ایمیج استاندارد بستهبندی میشود.
- هرجا Docker داشته باشید، اپلیکیشن را دقیقاً با همان شرایط اجرا میکنید.
- اسکیل کردن (بالا آوردن چند کانتینر) خیلی سادهتر میشود.
در این آموزش یاد میگیری:
- یک پروژه ساده Node.js (Express) بسازی.
- Dockerfile بنویسی و یک ایمیج سبک و Production-ready بسازی.
- از بهترین پراکتیسها استفاده کنی (multi-stage build، کاربر non-root، healthcheck و …).
- با Docker Compose چند سرویس (Node.js + Redis + Nginx) را با هم بالا بیاوری.
- ایمیج را روی Docker Hub پوش کنی و هر زمان خواستی دوباره از آن استفاده کنی.
پیشنیازها
برای دنبال کردن این آموزش، بهتر است موارد زیر را داشته باشی:
- یک سرور یا ماشین لینوکسی (مثلاً Ubuntu) یا WSL2 روی ویندوز
- نصب بودن Docker و Docker Compose
- نصب بودن Node.js و npm
- یک اکانت Docker Hub
- آشنایی مقدماتی با ترمینال
اگر محض تمرین روی لپتاپ خودت کار میکنی، همین که Docker Desktop نصب باشد کافی است.
قدم ۱ – ساخت اسکلت پروژه Node.js
یک دایرکتوری برای پروژه میسازیم. اسم ما node_project است؛ تو میتوانی هر نامی که دوست داشتی بگذاری:
mkdir node_project
cd node_project
ساخت package.json
فایل package.json را میسازیم و مشخصات پروژه و وابستگیها را داخلش مینویسیم:
nano package.json
محتوا:
{
"name": "nodejs-image-demo-fa",
"version": "1.0.0",
"description": "Node.js Docker image demo (Persian tutorial)",
"author": "Your Name <you@example.com>",
"license": "MIT",
"main": "app.js",
"keywords": [
"nodejs",
"docker",
"express"
],
"dependencies": {
"express": "^4.16.4"
}
}
حالا وابستگیها را نصب میکنیم:
npm install
این دستور Express را در پوشه پروژه نصب میکند.
قدم ۲ – ساخت اپلیکیشن Express ساده
میخواهیم یک وباپ ساده بسازیم که دو صفحه دارد:
- صفحه اصلی (
/) – معرفی کوتاه - صفحه
/sharks– اطلاعات بیشتر (مثال آموزشی)
فایل app.js
فایل اصلی اپلیکیشن را بساز:
nano app.js
محتوا:
const express = require('express');
const app = express();
const router = express.Router();
const path = __dirname + '/views/';
const port = 8080;
router.use(function (req, res, next) {
console.log('/' + req.method);
next();
});
router.get('/', function (req, res) {
res.sendFile(path + 'index.html');
});
router.get('/sharks', function (req, res) {
res.sendFile(path + 'sharks.html');
});
app.use(express.static(path));
app.use('/', router);
app.listen(port, function () {
console.log('Example app listening on port ' + port);
});
ساخت ساختار front-end
پوشهی ویوها را بساز:
mkdir views
فایل views/index.html
nano views/index.html
نمونه ساده (از Bootstrap استفاده میکنیم):
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Sharks</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link href="css/styles.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-dark bg-dark navbar-expand-md">
<div class="container">
<a class="navbar-brand" href="#">Everything Sharks</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active"><a href="/" class="nav-link">Home</a></li>
<li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a></li>
</ul>
</div>
</div>
</nav>
<div class="jumbotron">
<div class="container">
<h1>Want to Learn About Sharks?</h1>
<p>Are you ready to learn about sharks?</p>
<p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a></p>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6">
<h3>Not all sharks are alike</h3>
<p>Though some are dangerous, most shark species are harmless to humans.</p>
</div>
<div class="col-lg-6">
<h3>Sharks are ancient</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago.</p>
</div>
</div>
</div>
</body>
</html>
فایل views/sharks.html
nano views/sharks.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Shark Info</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link href="css/styles.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-dark bg-dark navbar-expand-md">
<div class="container">
<a class="navbar-brand" href="/">Everything Sharks</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a href="/" class="nav-link">Home</a></li>
<li class="nav-item active"><a href="/sharks" class="nav-link">Sharks</a></li>
</ul>
</div>
</div>
</nav>
<div class="jumbotron text-center">
<h1>Shark Info</h1>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6">
<p>
<div class="caption">Some sharks are not considered a threat to humans, like the sawshark.</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
</p>
</div>
<div class="col-lg-6">
<p>
<div class="caption">Other sharks are friendly – like Sammy the Shark!</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
</p>
</div>
</div>
</div>
</body>
</html>
استایل ساده در views/css/styles.css
mkdir -p views/css
nano views/css/styles.css
.navbar {
margin-bottom: 0;
}
body {
background: #020A1B;
color: #ffffff;
font-family: 'Merriweather', sans-serif;
}
h1, h2 {
font-weight: bold;
}
p {
font-size: 16px;
color: #ffffff;
}
.jumbotron {
background: #0048CD;
color: white;
text-align: center;
}
.jumbotron p {
color: white;
font-size: 26px;
}
.btn-primary {
color: #fff;
border-color: white;
margin-bottom: 5px;
}
img, video, audio {
margin-top: 20px;
max-width: 80%;
}
تست اپلیکیشن بدون Docker
node app.js
اگر روی سرور هستی و فایروال داری، باید پورت ۸۰۸۰ را باز کنی (روی Ubuntu):
sudo ufw allow 8080
حالا در مرورگر برو به:
http://your_server_ip:8080
اگر همهچیز درست باشد، صفحه اصلی را میبینی.
قدم ۳ – نوشتن Dockerfile (با best practiceهای ۲۰۲۵)
حالا وقت کانتینرسازی است. هدف:
- یک ایمیج سبک بسازیم.
- اپ در کانتینر با کاربر non-root اجرا شود.
- healthcheck داشته باشیم.
- از multi-stage build استفاده کنیم تا لایه build از runtime جدا باشد.
در روت پروژه، فایل Dockerfile را بساز:
nano Dockerfile
محتوا:
# مرحله build
FROM node:20-alpine AS builder
# دایرکتوری کاری
WORKDIR /app
# فقط فایلهای پکیج را کپی کن
COPY package*.json ./
# نصب وابستگیها (production فقط)
RUN npm ci --only=production && npm cache clean --force
# بقیه سورس را کپی کن
COPY . .
# مرحله production
FROM node:20-alpine AS production
WORKDIR /app
# ساخت کاربر non-root
RUN addgroup -g 1001 -S nodejs \
&& adduser -S appuser -u 1001
# کپی خروجی build با مالکیت درست
COPY --from=builder --chown=appuser:nodejs /app /app
# سوییچ به کاربر non-root
USER appuser
# اکسپوز پورت
EXPOSE 8080
# healthcheck برای مانیتور کانتینر
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:8080', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
# دستور اجرا
CMD ["node", "app.js"]
فایل .dockerignore
برای کوچککردن context و سریعتر شدن build:
nano .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
قدم ۴ – build و run کردن کانتینر
ساخت ایمیج Docker
فرض کنیم یوزرنیم Docker Hub تو your_dockerhub_username است:
sudo docker build -t your_dockerhub_username/nodejs-image-demo-fa .
بعد از اتمام build، ایمیج را ببین:
sudo docker images
اجرای کانتینر
کانتینر را روی پورت ۸۰ هاست بالا میآوریم و آن را به پورت ۸۰۸۰ داخل کانتینر مپ میکنیم:
sudo docker run --name nodejs-image-demo-fa -p 80:8080 -d your_dockerhub_username/nodejs-image-demo-fa
لیست کانتینرهای در حال اجرا:
sudo docker ps
حالا در مرورگر برو به:
http://your_server_ip
اپلیکیشن باید مثل قبل لود شود، فقط اینبار از داخل Docker.
قدم ۵ – Docker Compose برای چند سرویس (Node.js + Redis + Nginx)
در دنیای واقعی معمولاً فقط یک کانتینر نداری؛ مثلاً:
- سرویس Node.js
- دیتابیس یا کش (مثل Redis)
- یک reverse proxy مثل Nginx
Docker Compose کمک میکند همه اینها را در یک فایل YAML تعریف و با یک دستور بالا بیاوری.
ساخت docker-compose.yml
nano docker-compose.yml
نمونه کانفیگ:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- PORT=8080
depends_on:
- redis
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- app-network
restart: unless-stopped
volumes:
- redis_data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- app-network
restart: unless-stopped
volumes:
redis_data:
networks:
app-network:
driver: bridge
نکته: باید یک فایل
nginx.confمناسب هم بسازی تا درخواستها را به سرویسappفوروارد کند.
اجرای استک با Compose
docker compose up -d --build
- تمام سرویسها (app، redis، nginx) با هم بالا میآیند.
- Nginx روی پورت ۸۰ گوش میدهد و ترافیک را به
appپاس میدهد.
قدم ۶ – انتشار ایمیج روی Docker Hub
هدف این است که ایمیج را جایی ذخیره کنیم تا بعداً از هر سروری بتوانیم آن را pull و اجرا کنیم.
لاگین به Docker Hub
sudo docker login -u your_dockerhub_username
بعد از وارد کردن پسورد، حالا میتوانی ایمیج را push کنی:
sudo docker push your_dockerhub_username/nodejs-image-demo-fa
تست سناریوی «از صفر»
۱. تمام کانتینرها و ایمیجها را پاک کن (اختیاری، ولی آموزشی):
docker system prune -a
۲. ایمیج را از Docker Hub بکش:
docker pull your_dockerhub_username/nodejs-image-demo-fa
۳. دوباره کانتینر را بساز و اجرا کن:
docker run --name nodejs-image-demo-fa -p 80:8080 -d your_dockerhub_username/nodejs-image-demo-fa
دوباره با رفتن به http://your_server_ip اپلیکیشن را میبینی؛ بدون اینکه حتی سورسکد روی سرور باشد.
چند نکته مهم برای Production
انتخاب base image مناسب برای Node.js
در سالهای اخیر معمولاً این گزینهها رایجاند:
node:20-alpine→ سبک، مناسب بیشتر اپهای productionnode:20-slim→ بر پایه Debian، با پکیجهای بیشترnode:20-bullseye→ ایمیج کامل و سنگینتر
برای اکثر اپلیکیشنها node:20-alpine انتخاب خوبی است.
کاهش حجم ایمیج
- از multi-stage build استفاده کن.
node_modulesو فایلهای غیرضروری را در.dockerignoreحذف کن.- devDependencyها را داخل ایمیج production نصب نکن.
Resource limit در Docker Compose
برای مدیریت منابع در سرورهای شلوغ:
services:
app:
build: .
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
پرسشهای متداول (FAQ کوتاه)
۱. چرا برای Node.js از Docker استفاده کنم؟
- محیط یکسان بین dev، staging و production
- دپلوی ساده با یک ایمج و یک دستور
- ایزوله بودن وابستگیها و نسخه Node.js
- آماده برای اسکیل با Kubernetes و…
۲. چگونه Node.js را به دیتابیس داخل Docker وصل کنم؟
با Docker Compose:
services:
app:
build: .
environment:
- DATABASE_URL=postgresql://user:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
۳. چطور داخل کانتینر Node.js دیباگ کنم؟
-
لاگها:
codedocker logs -f nodejs-image-demo-fa -
شل گرفتن از کانتینر:
codedocker exec -it nodejs-image-demo-fa /bin/sh -
استفاده از VS Code Dev Containers یا remote debugging.
جمعبندی
در این آموزش:
- یک اپ ساده Node.js/Express ساختیم.
- آن را داخل Docker با یک Dockerfile مدرن (multi-stage، کاربر non-root، healthcheck) کانتینرسازی کردیم.
- با Docker Compose یک استک چندسرویسی (app + Redis + Nginx) بالا آوردیم.
- ایمیج را روی Docker Hub push کردیم و از آن برای اجرای دوباره اپلیکیشن استفاده کردیم.
این دقیقاً همان مسیری است که اکثر تیمها برای بردن اپلیکیشنهای Node.js از لپتاپ توسعهدهنده به محیط production استفاده میکنند.
اگر دوست داشتی، در یک پست جداگانه میتوانیم همین پروژه را به Kubernetes ببریم و مراحل دیپلوی روی K8s را هم مرحلهبهمرحله بنویسیم.