How to Deploy a Simple Node.js App with AWS Elastic Beanstalk

If you've built a Node.js app and want it running in production without first mastering EC2 instance types, VPC subnets, and load balancer listeners, AWS Elastic Beanstalk is the deployment surface you're looking for. It provisions and manages the underlying infrastructure automatically, letting you focus on shipping code.

TL;DR: What Elastic Beanstalk Does for Your Node.js App

ConcernYou HandleElastic Beanstalk Handles
Application code✅ Write and package it
EC2 provisioning✅ Selects and launches instances
Load balancer setup✅ Creates and configures ALB/CLB
Auto Scaling group✅ Manages scaling policies
OS patching✅ Managed platform updates
Health monitoring✅ Built-in health dashboard
Deployment strategyOptional override✅ Rolling, immutable, blue/green

How Elastic Beanstalk Works Under the Hood

Elastic Beanstalk is an orchestration layer, not a new compute primitive. When you upload your application bundle, Elastic Beanstalk creates a CloudFormation stack that provisions an Auto Scaling group, an Elastic Load Balancer, EC2 instances running the Node.js managed platform, security groups, and an S3 bucket for application versions — all wired together. The platform runtime (Node.js version, nginx proxy, process manager) is baked into an Amazon Machine Image maintained by AWS. Your code runs inside that environment; you never touch the AMI directly unless you use custom platform extensions.

graph LR Dev["Developer
eb deploy"] --> S3["S3 Bucket
Application Version"] S3 --> EB["Elastic Beanstalk
Environment Update"] EB --> CF["CloudFormation
Stack Change"] CF --> ASG["Auto Scaling Group
EC2 Instances"] ASG --> Agent["Host Manager Agent
npm install + start"] Agent --> NginX["nginx Proxy
PORT forwarding"] NginX --> ALB["Application Load Balancer
Health-checked routing"] ALB --> Users["End Users"]
  1. Developer uploads a ZIP/WAR via the console, CLI, or CI pipeline.
  2. Elastic Beanstalk stores the artifact in a managed S3 bucket as a versioned application version.
  3. Environment update triggers a CloudFormation stack change that pushes the new version to EC2 instances via the Beanstalk host manager agent.
  4. The load balancer routes traffic only to instances that pass health checks, enforcing zero-downtime during rolling deployments.
  5. Auto Scaling adds or removes instances based on the scaling triggers you configure (CPU, network, or a custom CloudWatch metric).

Prerequisites Before You Deploy

  • AWS account with sufficient IAM permissions (see IAM section below).
  • AWS CLI installed and configured (aws configure).
  • EB CLI installed: pip install awsebcli.
  • A Node.js app with a package.json at the project root.
  • A start script defined in package.json — Elastic Beanstalk's Node.js platform runs npm start by default.
Think of Elastic Beanstalk like a managed apartment building: AWS owns the plumbing, electrical, and structure. You furnish and decorate your unit (your app). You can request renovations (configuration options), but you don't rewire the building yourself.

Step 1: Structure Your Node.js Application Bundle

Elastic Beanstalk's Node.js platform expects your application root to contain package.json. The platform agent runs npm install --production on deployment, so you do not need to commit node_modules. The entry point is whatever npm start resolves to in your scripts block. Your app must listen on the port provided by the PORT environment variable (Elastic Beanstalk sets this; the nginx reverse proxy forwards traffic to it).

// app.js — minimal Express example
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => res.send('Hello from Elastic Beanstalk'));

app.listen(port, () => console.log(`Listening on port ${port}`));
// package.json (relevant section)
{
  "scripts": {
    "start": "node app.js"
  }
}

Create a .ebignore file (same syntax as .gitignore) to exclude node_modules, test files, and local config from the deployment bundle. If no .ebignore exists, the EB CLI falls back to .gitignore.

# .ebignore
node_modules/
.env
*.test.js

Step 2: Initialize the Elastic Beanstalk Application

Initializing the EB application creates the application record in Elastic Beanstalk and writes a .elasticbeanstalk/config.yml file locally. This step does not yet provision any infrastructure — that happens when you create an environment in Step 3. Running eb init interactively is the clearest path for a first deployment because it validates your AWS credentials and lists available Node.js platform versions.

eb init my-nodejs-app \
  --platform "Node.js 20 running on 64bit Amazon Linux 2023" \
  --region us-east-1

Verify the generated config before proceeding:

cat .elasticbeanstalk/config.yml

Expected output includes application_name, default_platform, and default_region. If the platform string is wrong here, deployments will fail silently at the environment creation stage — fix it now rather than after infrastructure is provisioned.

Step 3: Create the Environment and Deploy

Creating an environment is the step that actually provisions EC2, the load balancer, and the Auto Scaling group. The --single flag creates a single-instance environment without a load balancer — useful for development to avoid ALB costs, but not suitable for production. For production, omit --single and Elastic Beanstalk provisions a load-balanced, auto-scaled environment by default.

# Development: single instance (no load balancer)
eb create my-nodejs-dev --single --region us-east-1
# Production: load-balanced environment
eb create my-nodejs-prod --region us-east-1

Environment creation takes 3–5 minutes. The EB CLI streams events to your terminal. Watch for Successfully launched environment and a green health status before proceeding.

After the environment is healthy, deploy updated code with:

eb deploy my-nodejs-prod

Open the deployed application in your browser:

eb open my-nodejs-prod

Step 4: Configure Environment Variables

Never hardcode secrets in your application bundle. Elastic Beanstalk injects environment variables into the EC2 instance's process environment, making them available to your Node.js process via process.env. Set them via the CLI rather than the console to keep configuration reproducible and auditable.

aws elasticbeanstalk update-environment \
  --environment-name my-nodejs-prod \
  --option-settings \
    Namespace=aws:elasticbeanstalk:application:environment,OptionName=NODE_ENV,Value=production \
    Namespace=aws:elasticbeanstalk:application:environment,OptionName=DB_HOST,Value=your-db-host \
  --region us-east-1

Each update-environment call that changes option settings triggers an environment update, which briefly cycles instances. Batch all variable changes into a single call to avoid multiple rolling restarts.

Step 5: Verify Health and Inspect Logs

The most common first-deployment failure is a healthy-looking environment that returns a 502 from the load balancer. The app launched, nginx is running, but your Node.js process crashed on startup — and the Elastic Beanstalk health dashboard shows "Degraded" or "Severe" without an obvious error message in the console. This is where engineers lose 30 minutes checking security groups when the real issue is a missing environment variable or a package.json start script that doesn't match the entry file name.

Pull the full application logs to see the actual Node.js stderr output:

eb logs my-nodejs-prod

For targeted inspection, retrieve logs to a local directory:

eb logs my-nodejs-prod --zip

The critical log file is /var/log/web.stdout.log (stdout/stderr from your Node.js process) and /var/log/nginx/error.log (proxy-level errors). If web.stdout.log shows a module not found error, your .ebignore may be excluding a required file, or a dependency is listed under devDependencies instead of dependencies.

Check environment health status programmatically:

aws elasticbeanstalk describe-environment-health \
  --environment-name my-nodejs-prod \
  --attribute-names All \
  --region us-east-1

IAM Permissions Required

Elastic Beanstalk requires two IAM roles: a service role (used by Elastic Beanstalk itself to call EC2, ELB, and Auto Scaling on your behalf) and an instance profile (attached to EC2 instances so they can pull application versions from S3 and write logs to CloudWatch). The AWS-managed policies AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy and AWSElasticBeanstalkWebTier cover the standard cases. For the deploying IAM user or role, the minimum permissions needed are shown below.

🔽 Click to expand — Deployer IAM Policy (minimum permissions)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ElasticBeanstalkDeploy",
      "Effect": "Allow",
      "Action": [
        "elasticbeanstalk:CreateApplication",
        "elasticbeanstalk:CreateEnvironment",
        "elasticbeanstalk:UpdateEnvironment",
        "elasticbeanstalk:CreateApplicationVersion",
        "elasticbeanstalk:DescribeEnvironments",
        "elasticbeanstalk:DescribeEnvironmentHealth",
        "elasticbeanstalk:RequestEnvironmentInfo",
        "elasticbeanstalk:RetrieveEnvironmentInfo",
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": "*"
    }
  ]
}

Read and Describe actions in Elastic Beanstalk frequently require "Resource": "*" because resource-level permission support varies by action. Always verify in the Service Authorization Reference before tightening ARN restrictions.

Deployment Strategies: Choosing the Right One

Elastic Beanstalk supports multiple deployment policies. The choice has direct consequences for availability and deployment speed — picking the wrong one for production is a common operational mistake.

graph TD Start["New Deployment Triggered"] --> Q1{"Environment Type?"} Q1 -->|"Development"| AllAtOnce["All at Once
Fastest, causes downtime"] Q1 -->|"Production"| Q2{"Capacity Priority?"} Q2 -->|"Can tolerate reduced capacity"| Rolling["Rolling
No extra cost"] Q2 -->|"Must maintain full capacity"| Q3{"Rollback speed?"} Q3 -->|"Standard"| RollingBatch["Rolling with Additional Batch
Full capacity maintained"] Q3 -->|"Instant rollback needed"| Immutable["Immutable
New ASG, safest rollback"] Q3 -->|"Canary / gradual cutover"| TrafficSplit["Traffic Splitting
Configurable % to new version"]
  1. All at once: Deploys to all instances simultaneously. Fastest, but causes downtime. Use only in development.
  2. Rolling: Deploys to a batch of instances at a time. Reduces capacity during deployment. No additional instances launched.
  3. Rolling with additional batch: Launches a new batch first, then rolls. Maintains full capacity throughout. Costs slightly more during deployment.
  4. Immutable: Launches a full new set of instances in a new Auto Scaling group, then swaps. Safest rollback path — terminate the new group on failure.
  5. Traffic splitting: Canary-style deployment. Sends a configurable percentage of traffic to the new version before full cutover.

Set the deployment policy via option settings:

aws elasticbeanstalk update-environment \
  --environment-name my-nodejs-prod \
  --option-settings \
    Namespace=aws:elasticbeanstalk:command,OptionName=DeploymentPolicy,Value=RollingWithAdditionalBatch \
    Namespace=aws:elasticbeanstalk:command,OptionName=BatchSizeType,Value=Percentage \
    Namespace=aws:elasticbeanstalk:command,OptionName=BatchSize,Value=25 \
  --region us-east-1

Advanced Configuration with .ebextensions

When the standard Elastic Beanstalk options aren't enough — custom nginx configuration, additional OS packages, or pre-deployment scripts — use .ebextensions. Place YAML configuration files in a .ebextensions/ directory at your project root. They are processed in alphabetical order during environment creation and deployment.

# .ebextensions/01_packages.config
packages:
  yum:
    git: []
# .ebextensions/02_env.config
option_settings:
  aws:elasticbeanstalk:application:environment:
    NODE_OPTIONS: "--max-old-space-size=512"

For Node.js platform-specific configuration on Amazon Linux 2 and Amazon Linux 2023 platforms, use a Procfile to override the default process command, or a .platform/nginx/conf.d/ directory for nginx configuration overrides — the .ebextensions container_commands approach for nginx is the older pattern and behaves differently on AL2023.

Elastic Beanstalk Node.js Deployment: Wrap-Up and Next Steps

Elastic Beanstalk removes the infrastructure bootstrapping burden for Node.js deployments without locking you out of the underlying AWS resources. The EC2 instances, load balancer, and Auto Scaling group are all visible and accessible in your account — Elastic Beanstalk just manages their lifecycle. As your application grows, you can graduate individual components: swap the managed database for RDS with Multi-AZ, add a CloudFront distribution in front of the load balancer, or migrate to ECS when container-level control becomes necessary.

Recommended next steps:

  • Enable managed platform updates to keep the Node.js runtime patched automatically.
  • Configure enhanced health reporting for per-instance metrics in CloudWatch.
  • Add an RDS database — but decouple it from the Elastic Beanstalk environment so it survives environment termination.
  • Review the official Elastic Beanstalk developer guide for platform-specific tuning options.

Glossary

TermDefinition
ApplicationThe top-level Elastic Beanstalk container that holds application versions and environments.
EnvironmentA running instance of an application version — includes EC2, load balancer, and Auto Scaling group.
Application VersionA labeled ZIP artifact stored in S3, representing a specific deployable build of your code.
PlatformThe managed runtime stack (OS + language runtime + proxy) that Elastic Beanstalk provisions on EC2.
Instance ProfileAn IAM role attached to EC2 instances, granting them permissions to interact with AWS services.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?