본문으로 건너뛰기 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

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Deploy Node.js on AWS: EC2 bootstrap with PM2 and Nginx, Elastic Beanstalk with eb CLI, and serverless Lambda with serve…

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers AWS, EC2, Node.js, Deployment, Elastic Beanstalk, Lambda, Nginx, PM2.