AWS Elastic Beanstalk is one of the fastest ways to get a Node.js application running in a production-grade environment with load balancing, auto scaling, health monitoring, and managed OS updates — without managing EC2 instances directly. In this guide I'll walk through the full setup: deploying the app, attaching a custom domain with SSL, and wiring up automated deployments via GitHub Actions.
Prerequisites: AWS account, Node.js app, domain registered in Route 53 or transferable to it, GitHub repository, AWS CLI installed.
Elastic Beanstalk expects your app to listen on the port exposed by the PORT environment variable (it injects this automatically).
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Ensure your package.json has a start script:
{
"scripts": {
"start": "node server.js"
},
"engines": {
"node": ">=20"
}
}
Important: Add node_modules/ to .gitignore. Beanstalk runs npm install on deploy — don't include node_modules in your zip.
npm install -g aws-elastic-beanstalk-cli # if not installed
eb init my-node-app --platform node.js --region ca-central-1
eb create production --elb-type application
eb open
Never hard-code secrets. Set them as Beanstalk environment properties:
eb setenv NODE_ENV=production \
DATABASE_URL=postgresql://... \
JWT_SECRET=your-secret \
OPENAI_API_KEY=sk-...
Or via Console: Configuration → Software → Environment properties. These are injected as process.env.* at runtime.
# Route 53 → Hosted Zone → Create Record
Name: api.yourdomain.com
Type: A (Alias)
Target: Alias to Elastic Beanstalk environment → select your env
Using an Alias A record (not CNAME) avoids extra DNS lookups and works at the zone apex (yourdomain.com).
In Beanstalk: Configuration → Capacity → Edit
Environment type: Load balanced
Min instances: 2
Max instances: 6
Scaling trigger:
Metric: CPUUtilization
Upper: 70% → scale out
Lower: 30% → scale in
Period: 5 minutes
Two minimum instances ensures high availability across two Availability Zones. The load balancer health check endpoint (/health) should return HTTP 200 within 10 seconds or Beanstalk will replace the instance.
Create .github/workflows/deploy.yml:
name: Deploy to Elastic Beanstalk
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install & test
run: |
npm ci
npm test
- name: Generate deployment package
run: zip -r deploy.zip . -x "*.git*" -x "node_modules/*"
- name: Deploy to Elastic Beanstalk
uses: einaregilsson/beanstalk-deploy@v22
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
application_name: my-node-app
environment_name: production
region: ca-central-1
version_label: ${{ github.sha }}
deployment_package: deploy.zip
Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as GitHub repository secrets. Use an IAM user with only AWSElasticBeanstalkFullAccess — never use root credentials in CI.
You now have a Node.js app running on Elastic Beanstalk with HTTPS, a custom domain, auto scaling across multiple AZs, and zero-downtime deployments triggered on every push to main. This is the exact stack I've used across multiple production projects handling thousands of daily requests.
Need help setting up your AWS infrastructure? Get in touch or hire me on Upwork.