본문으로 건너뛰기
Previous
Next
AWS Deployment for Node.js | EC2

AWS Deployment for Node.js | EC2

AWS Deployment for Node.js | EC2

이 글의 핵심

Deploy Node.js on AWS: EC2 bootstrap with PM2 and Nginx, Elastic Beanstalk with eb CLI, and serverless Lambda with serverless-http. Security groups, env vars, HTTPS, and rollout strategies.

Three AWS Deployment Paths

AWS offers a spectrum of control vs convenience for Node.js applications:

OptionControlMaintenanceBest for
EC2FullYou manage OS, runtime, web serverLearning, custom configs, cost optimization
Elastic BeanstalkMediumAWS manages infra, you manage appStandard web apps without Kubernetes complexity
LambdaLowAWS manages everythingSpiky/low traffic, microservices, event-driven

Option 1: EC2 with PM2 and Nginx

EC2 gives you a virtual machine. You install Node.js, PM2 for process management, and Nginx as a reverse proxy.

Launch an EC2 Instance

  1. AWS Console → EC2 → Launch Instance
  2. Choose Ubuntu Server 22.04 LTS (or 24.04 LTS)
  3. Instance type: t3.micro (free tier) or t3.small for production
  4. Security Group: create rules:
    • SSH (22) — from your IP only, not 0.0.0.0/0
    • HTTP (80) — from anywhere (0.0.0.0/0)
    • HTTPS (443) — from anywhere
  5. Key pair: create and download a .pem file
  6. Launch

Initial Server Setup

# Connect via SSH
ssh -i ~/keys/myapp.pem ubuntu@<your-ec2-public-ip>

# Update packages
sudo apt-get update && sudo apt-get upgrade -y

# Install Node.js 20 LTS via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# Verify
node --version    # v20.x.x
npm --version

# Install git
sudo apt-get install -y git

Deploy Your Application

# Clone the repository
git clone https://github.com/yourusername/your-app.git
cd your-app

# Install production dependencies only
npm ci --only=production

# Create .env file
cat > .env << 'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@db-host:5432/mydb
JWT_SECRET=your-secret-here
EOF

# Restrict .env permissions
chmod 600 .env

PM2 Process Management

# Install PM2 globally
sudo npm install -g pm2

# Start the application (cluster mode uses all CPU cores)
pm2 start app.js --name "my-app" -i max

# Or use an ecosystem config file for better control
cat > ecosystem.config.js << 'EOF'
module.exports = {
    apps: [{
        name: 'my-app',
        script: 'app.js',
        instances: 'max',        // one per CPU core
        exec_mode: 'cluster',
        env: {
            NODE_ENV: 'production',
            PORT: 3000
        },
        max_memory_restart: '500M',  // restart if memory exceeds 500MB
        error_file: './logs/err.log',
        out_file: './logs/out.log',
        log_file: './logs/combined.log',
    }]
};
EOF

pm2 start ecosystem.config.js

# Set up startup script (runs PM2 on server reboot)
pm2 startup
# Run the command it prints (something like: sudo env PATH=...)
pm2 save

# Useful PM2 commands
pm2 list           # show all processes
pm2 logs           # tail all logs
pm2 restart my-app  # restart without downtime
pm2 reload my-app   # zero-downtime reload (cluster mode)
pm2 monit           # real-time monitoring dashboard

Nginx Configuration

sudo apt-get install -y nginx

# Create site config
sudo tee /etc/nginx/sites-available/my-app << 'EOF'
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    # Serve static files directly — bypass Node.js
    location /static/ {
        alias /home/ubuntu/your-app/public/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Proxy everything else to Node.js
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';  # WebSocket support
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}
EOF

# Enable the site
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t        # validate config
sudo systemctl reload nginx

HTTPS with Let’s Encrypt

sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# Certbot automatically modifies the nginx config for HTTPS
# Auto-renewal is set up automatically
sudo certbot renew --dry-run  # test renewal

Deployment Updates

# Update script — run on server
cd /home/ubuntu/your-app
git pull origin main
npm ci --only=production

# Build if needed (TypeScript, etc.)
npm run build

# Zero-downtime reload (PM2 cluster mode)
pm2 reload my-app

Option 2: Elastic Beanstalk

Elastic Beanstalk manages the EC2 instances, load balancer, auto-scaling, and deployment for you. You focus on the application.

Setup

# Install EB CLI
pip install awsebcli --upgrade

# Configure AWS credentials
aws configure
# Enter: Access Key ID, Secret Access Key, region (e.g. us-east-1), output format (json)

# In your project directory
cd your-app
eb init

# Select:
# - Region (us-east-1 or ap-northeast-2 for Korea)
# - Application name
# - Platform: Node.js 20 (running on 64bit Amazon Linux 2023)
# - Whether to use CodeCommit: No (use GitHub)

Create Environment and Deploy

# Create a production environment (this launches EC2 instances + load balancer)
eb create production --instance-type t3.small --min-instances 1 --max-instances 3

# Deploy current code
eb deploy

# Check status
eb status

# Tail logs
eb logs --all

# SSH into the instance for debugging
eb ssh

Environment Variables

# Set environment variables (replaces .env file)
eb setenv \
    NODE_ENV=production \
    PORT=8080 \
    DATABASE_URL=postgresql://user:password@db-host:5432/mydb \
    JWT_SECRET=your-secret-here

# View current env vars
eb printenv

Elastic Beanstalk Procfile

# Procfile — tells EB how to start your app
web: node app.js

Or with npm:

web: npm start

Health Check Endpoint

Elastic Beanstalk pings / by default for health checks. Add a dedicated endpoint:

// In your Express app
app.get('/health', (req, res) => {
    res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});

Then configure EB to use /health:

eb config
# Set HealthCheckPath: /health under aws:elasticbeanstalk:application

Option 3: AWS Lambda (Serverless)

Lambda runs your code in response to events. For HTTP APIs, use API Gateway → Lambda.

Wrap Your Express App

// lambda.js
const serverless = require('serverless-http');
const app = require('./app');  // your Express app

module.exports.handler = serverless(app);
npm install --save serverless-http

serverless.yml Configuration

service: my-app
provider:
  name: aws
  runtime: nodejs20.x
  region: ap-northeast-2
  timeout: 30
  memorySize: 512
  environment:
    NODE_ENV: production
    DATABASE_URL: ${ssm:/my-app/database-url}  # from AWS SSM Parameter Store

functions:
  app:
    handler: lambda.handler
    events:
      - http:
          path: /
          method: ANY
          cors: true
      - http:
          path: /{proxy+}
          method: ANY
          cors: true

Deploy

npm install -g serverless

serverless deploy --stage production

# View logs
serverless logs -f app --tail

# Invoke manually
serverless invoke -f app --data '{"httpMethod":"GET","path":"/"}'

# Tear down
serverless remove --stage production

Cold Start Mitigation

Lambda has cold starts (first invocation after idle takes extra time). Reduce impact:

# Provisioned concurrency — pre-warm instances (adds cost)
functions:
  app:
    handler: lambda.handler
    provisionedConcurrency: 2

Or use a CloudWatch Events rule to ping Lambda every 5 minutes:

events:
  - schedule:
      rate: rate(5 minutes)
      enabled: true
      input:
        source: warm-up

Choosing Between the Three

Low traffic / spiky / event-driven
  → Lambda

Standard web app, team unfamiliar with containers
  → Elastic Beanstalk

Custom config, cost-sensitive, or needs containers later
  → EC2 + PM2 + Nginx
  → EC2 + Docker (next step)
  → ECS / EKS (after Docker)

Key Takeaways

  • EC2 + PM2 + Nginx: full control, requires most ops work. PM2 handles cluster mode and auto-restart; Nginx handles HTTPS, static files, and proxying; Let’s Encrypt is free SSL
  • Elastic Beanstalk: AWS manages infrastructure. Deploy with eb deploy, set env vars with eb setenv. Add a /health endpoint for health checks
  • Lambda: pay per request, no idle cost, auto-scales. Use serverless-http to wrap Express. Cold starts are real — use provisioned concurrency for latency-sensitive APIs
  • Security: security groups should only open the ports you use. Never put secrets in code — use environment variables, AWS SSM Parameter Store, or Secrets Manager
  • Zero-downtime deploys: PM2 cluster mode supports pm2 reload (graceful); Elastic Beanstalk supports rolling deployments out of the box

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Deploy Node.js on AWS: EC2 bootstrap with PM2 and Nginx, Elastic Beanstalk with eb CLI, and serverless Lambda with serve… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

AWS, EC2, Node.js, Deployment, Elastic Beanstalk, Lambda, Nginx, PM2 등으로 검색하시면 이 글이 도움이 됩니다.