Shipping the first microservice to production, with managed PaaS.
About this module
This is the module for the project's first real deploy. You'll ship the Orders service on Beanstalk and understand the release cycle, health checks, logs, and troubleshooting. This is where theory meets the day-to-day operations of AWS.
Level: Intermediate · Estimated duration: 4-5 hours
Table of Contents
- What Beanstalk does for you
- Anatomy of a Beanstalk application
- Environments: dev, staging, prod
- Preparing the Orders service
- Configuring IAM for Beanstalk
- First deploy
- Health checks and auto-recovery
- Logs and troubleshooting
- Updating the service (continuous deployment)
- Environment variables and configuration
- Practice exercises
- Next steps
01. What Beanstalk does for you
Before you ship anything, it's important to understand exactly what Beanstalk automates. Without that, you'll use a powerful tool as a black box — and when something goes wrong, you'll be lost.
The problem it solves
Deploying a Node.js application on AWS without Beanstalk involves: creating a VPC, subnets, security groups, launching an EC2 instance, installing Node, configuring PM2 or systemd, setting up nginx or an ALB, configuring auto-scaling, setting up deployment via SSH or a pipeline — an enormous number of decisions and configurations for something simple.
Beanstalk takes all of that and hands it to you as a platform. You upload a zip with your code, and it takes care of the rest.
What it provisions automatically
- EC2 instances with the correct runtime (Node.js 20, Python, Java, etc.).
- Application Load Balancer with health check and SSL.
- Auto Scaling Group with configurable rules.
- CloudWatch for logs and metrics.
- Security Groups with secure defaults.
- S3 bucket to store application versions.
- Notifications via SNS for environment state changes.
What it does NOT do
- It doesn't create your database (RDS can be integrated, but it's optional).
- It doesn't write your code for you.
- It doesn't replace good observability practices — you still need to instrument logs.
- It doesn't choose the architecture for you (single-instance vs load-balanced).
02. Anatomy of a Beanstalk application
Beanstalk's vocabulary can be confusing at first. Let's clear it up.
The three levels of hierarchy
- Application — the logical name of your system. Example: orders-service. An application contains one or more versions and environments.
- Application Version — a code package (zip) at a specific version. Example: v1.0.0, v1.1.0. Each deploy creates a new version.
- Environment — a running instance of the application. You can have multiple environments for the same application: orders-dev, orders-prod.
Environment types
| Type | When to use | Components |
|---|---|---|
| Web Server | APIs and HTTP applications | EC2 + ALB + ASG |
| Worker | Background task processing via SQS | EC2 + SQS + ASG |
For our project, all microservices are Web Server — they receive HTTP requests. Worker environments are interesting for asynchronous processing, but we'll prefer Lambda for that role in this project.
Single-instance vs load-balanced
When you create an environment, you choose between two modes:
- Single-instance — a single EC2, no ALB. Cheap (no ALB cost) and simple. Ideal for dev/study.
- Load-balanced — ALB + Auto Scaling with 1+ instances. Resilient. Costs ~US$16/month more (the ALB).
Platform and version
Each environment uses a platform — basically a pre-configured AMI. We'll use Node.js 20 running on 64bit Amazon Linux 2023. AWS updates these platforms periodically; you can (and should) update when new versions are released.
03. Environments: dev, staging, prod
Much of modern DevOps discipline comes down to separating environments. Beanstalk makes this enormously easier.
Why separate environments?
- Isolation — a bug in dev doesn't affect prod.
- Independent configuration — different endpoints, credentials, and capacity.
- Progressive pipeline — code moves dev → staging → prod, with validation at each stage.
- Controlled costs — dev uses a smaller instance; prod uses what it needs.
Strategy for the project
For the study, we'll use only the dev environment of each service. In real projects you'd have all three (dev, staging, prod) — but the setup is identical, multiplied by 3. There's no learning gain in doing that now.
Naming convention
We'll adopt the pattern:
Application name: <service>-service
Environment name: <service>-<environment>
# Examples:
orders-service / orders-dev
inventory-service / inventory-dev
payments-service / payments-dev
Be consistent. Beanstalk uses these names in URLs, in logs, in events — so any inconsistency will confuse you later.
04. Preparing the Orders service
Before deploying, you need a minimal Node.js application running. Here we'll define what needs to be ready and guide you through the structure — but you write the code.
Suggested project structure
orders-service/
├── src/
│ ├── index.js # entrypoint
│ ├── routes/
│ │ └── orders.js # REST routes
│ ├── services/
│ │ └── ordersDb.js # DynamoDB access
│ └── middleware/
│ └── errorHandler.js
├── package.json
├── package-lock.json
├── .ebignore # files to NOT include in the zip
└── README.md
Minimum package.json requirements
- start script — Beanstalk runs npm start by default.
- node engines — declare the Node version compatible with the platform.
- express or an equivalent HTTP framework.
- @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb to access tables.
What your application must satisfy
- Listen on the port given by process.env.PORT — Beanstalk sets this variable (usually 8080).
- A GET / or GET /health endpoint returning 200 — for the health check.
- Logs to stdout/stderr — Beanstalk captures them automatically for CloudWatch.
- Don't persist anything locally — instances are ephemeral; use DynamoDB or S3.
- Endpoints implementing the contracts from section 7 of Module 01.
The .ebignore file
It works like .gitignore: it lists files that should not be included in the deploy zip. Always exclude:
node_modules/
.git/
.env
.env.local
*.log
.DS_Store
coverage/
tests/
Don't include node_modules — Beanstalk runs npm install automatically after the upload.
Validating locally
Before doing any deploy, your application needs to run cleanly locally:
cd orders-service
npm install
PORT=8080 npm start
# In another terminal:
curl http://localhost:8080/health
# expected response: {"status":"ok"} or similar05. Configuring IAM for Beanstalk
Before deploying, we need to make sure that: (a) Beanstalk has permission to create AWS resources; (b) the EC2 instances have permission to access DynamoDB.
The Beanstalk service role
When Beanstalk needs to create EC2 instances, configure the ALB, or write to CloudWatch, it assumes a service role. By default, AWS creates this role automatically the first time (called aws-elasticbeanstalk-service-role).
Instance profile (more important)
This is the role the EC2 instances will assume. This is where you give the Orders service permission to access the Orders DynamoDB table.
AWS automatically creates a role called aws-elasticbeanstalk-ec2-role with basic Beanstalk permissions. You need to add specific permission for DynamoDB.
Creating the custom policy
- Go to IAM > Policies > Create policy.
- Use the JSON editor and paste the policy below.
- Replace 123456789012 with your account ID.
- Policy name: orders-service-dynamodb-access.
- In IAM > Roles, find aws-elasticbeanstalk-ec2-role.
- Click Add permissions > Attach policies and add the policy you created.
Policy JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789012:table/Orders",
"arn:aws:dynamodb:us-east-1:123456789012:table/Orders/index/*"
]
}
]
}When we ship Inventory and Payments, we'll create analogous policies for those tables. Each service, its own policy.
06. First deploy
The moment of truth. Let's ship the Orders service on Beanstalk.
Creating the zip package
In your project's root directory:
cd orders-service
# Make sure .ebignore exists and is correct
cat .ebignore
# Create the zip, excluding what's in .ebignore
zip -r orders-v1.zip . -x '@.ebignore'
# Check the contents (it must NOT contain node_modules!)
unzip -l orders-v1.zip | headCreating the application in the console
- AWS Console > Elastic Beanstalk.
- Confirm region us-east-1.
- Click Create application.
- Application name: orders-service.
- Tags: Project=order-system, Environment=dev.
- Platform: Node.js 20 running on 64bit Amazon Linux 2023.
- Application code: Upload your code > select orders-v1.zip.
- Version label: v1.0.0.
- Presets: choose Single instance (Free Tier eligible).
- Click Next to continue with the detailed configuration.
Detailed configuration (important)
- Service role: use the default aws-elasticbeanstalk-service-role.
- EC2 instance profile: use aws-elasticbeanstalk-ec2-role (with the DynamoDB policy we created).
- Instance type: t2.micro (Free Tier).
- Instance metadata service: version v2 (more secure).
- Root volume type: General Purpose (SSD), 8 GB.
- Environment name: orders-dev.
- Environment properties (environment variables): we'll define these in section 10.
- Review everything and click Submit.
Following the deploy
The environment takes 5 to 10 minutes to create. You'll see events streaming in real time:
- Created CloudWatch alarms
- Created security group
- Created EC2 instance(s)
Application available at <url>- Environment health: Ok / Warning / Severe
07. Health checks and auto-recovery
Beanstalk continuously monitors your application. Understanding how health checks work is crucial for diagnosing problems quickly.
The two types of health check
| Type | What it checks | Frequency |
|---|---|---|
| Basic | Only whether the EC2 is responding | Every 30s |
| Enhanced | EC2 + ALB + metrics + app logs | Every 10s |
Always use Enhanced. It's free (included in the Free Tier) and gives you far superior visibility.
Health states
| State | Meaning |
|---|---|
| Ok | Everything working normally |
| Warning | Some issue detected, but not critical |
| Degraded | Degraded performance (e.g., too many 5xx) |
| Severe | Serious problem — possibly down |
| Pending | Change in progress (deploy, scaling) |
| Unknown | Beanstalk can't determine the state |
Health check path
By default, the ALB does a GET on the path /. If your application has a specific endpoint, configure it in Configuration > Load balancer > Health check path. I recommend:
Health check path: /health
// Suggested implementation (example only):
app.get('/health', (req, res) => {
// Ideally, do a real check:
// - Is DynamoDB responding?
// - Are critical dependencies ok?
res.json({ status: 'ok' });
});
Auto-recovery
When an EC2 instance fails (or the health check fails for a while), Beanstalk automatically terminates the instance and creates a new one. In single-instance environments, this means 1-2 minutes of downtime. In load-balanced ones, traffic is redirected to other instances while the sick one is replaced.
08. Logs and troubleshooting
Sooner or later something will go wrong. Knowing where to look is what separates 30 minutes of debugging from 3 hours.
Where the logs are
- Environment console > Logs > Request logs — grabs snapshots of the logs.
- CloudWatch Logs >
/aws/elasticbeanstalk/<env-name>/...— continuous stream (needs to be enabled). - SSH into the EC2 > /var/log/ — as a last resort, direct debugging.
Enabling CloudWatch Logs
- Environment > Configuration > Software > Edit.
- Under CloudWatch Logs, check Log streaming.
- Retention: 7 days (enough for the study).
- Lifecycle: Keep logs after terminating environment? No.
- Save.
The log files that matter
| File | What it contains |
|---|---|
| nodejs/nodejs.log | Your application's stdout/stderr |
| nginx/access.log | Incoming HTTP requests |
| nginx/error.log | nginx errors (reverse proxy) |
| eb-engine.log | Beanstalk deploy logs |
| eb-hooks.log | Pre/post deploy hooks |
Common errors on the first deploy
- Health check failing — The application didn't listen on port 8080, or the /health endpoint returns != 200.
- Application Version not found — Corrupted zip or size exceeding the limit. Rebuild the zip.
- Permission denied (DynamoDB) — The policy wasn't attached to the instance role. Review section 5.
- npm install fails — package.json with an invalid version or a private dependency without credentials.
- Process exits — Your application is crashing. Check nodejs.log.
09. Updating the service
Shipping the first version is easy. Shipping the 50th version reliably is what separates a toy system from a production system.
How to update via the console
- Make the changes in your local code.
- Create a new zip: orders-v1.1.zip.
- Environment > Upload and deploy.
- Select the new zip.
- Version label: v1.1.0 (always increment).
- Click Deploy.
Deploy strategies
| Strategy | How it works | Risk |
|---|---|---|
| All at once | Updates all instances at the same time | Downtime during the deploy |
| Rolling | Updates in batches | Temporarily reduced capacity |
| Rolling with batch | Launches new ones, then removes old ones | Extra cost during deploy |
| Immutable | Creates a parallel ASG, swaps after validation | Slower, but safe |
In single-instance, you can only use All at once (there are no other instances to do rolling). In production load-balanced, Immutable is the safest — zero downtime, easy rollback.
Rollback
If something goes wrong after a deploy, you can revert to a previous version:
- Environment > Application Versions.
- Find the previous stable version.
- Click Deploy next to it.
10. Environment variables and configuration
Your application can't have hardcoded configuration. Endpoints, table names, feature flags — everything should come from environment variables.
Configuring in Beanstalk
- Environment > Configuration > Updates, monitoring, and logging > Edit.
- Go to the Environment properties section.
- Add key-value pairs.
- Save. Beanstalk restarts the application to apply them.
Typical variables for the Orders service
AWS_REGION=us-east-1
DYNAMODB_TABLE_NAME=Orders
LOG_LEVEL=info
NODE_ENV=production
PAYMENTS_SERVICE_URL=https://payments-dev.us-east-1.elasticbeanstalk.com
SNS_ORDER_CREATED_TOPIC_ARN=arn:aws:sns:us-east-1:123456789012:order-created
In code, access them via process.env:
// Just an example of how to access — you implement the logic.
const region = process.env.AWS_REGION;
const tableName = process.env.DYNAMODB_TABLE_NAME;
// Always validate:
if (!tableName) {
throw new Error('DYNAMODB_TABLE_NAME not set');
}Secrets vs configuration
For common information (URL, log level), environment variables are fine. For secrets (API keys, passwords), the ideal is to use AWS Secrets Manager or SSM Parameter Store. We'll cover that in future modules — for now, IAM roles handle DynamoDB usage without needing credentials.
11. Practice exercises
Ship the Orders service
Implement the Orders service with at least the endpoints POST /v1/orders, GET /v1/orders/:id, and GET /health. Do the full deploy following sections 4-6. Confirm that: (a) the environment is healthy; (b) you can create an order via curl; (c) the order shows up in the DynamoDB table.
Force an error
Deploy a version with an intentional bug (e.g., a throw in app.listen). Observe: what changes in the dashboard? How long until it's detected? Where does the error show up in the logs? Roll back to the previous version.
Configuration via env
Modify your service to read the table name from a DYNAMODB_TABLE_NAME variable. Create the Orders-test table in DynamoDB. Change the env var in Beanstalk to point to the new table. Confirm that new orders go to Orders-test, without changing a single line of code.
Ship Inventory and Payments too
Repeat the whole process for the other two services. Create applications, IAM policies (each service with access only to its own table), and environments. In the end, you should have three Beanstalk URLs responding independently.
Reflection: the limits of Beanstalk
In free-form text (10-15 lines), describe: “At what point does Beanstalk start to fall short? What kind of need would drive you to migrate to ECS or EKS?” Think about scale, costs, complexity.
12. Next steps
An important milestone: you have three microservices running in production on AWS. In the next module, we'll eliminate all the manual console work.
What you learned
- Anatomy of a Beanstalk application: application, version, environment.
- Configuring IAM roles and instance profiles with least privilege.
- Doing the first deploy of a Node.js microservice.
- Health checks (basic vs enhanced) and auto-recovery.
- Where to find logs and how to diagnose common problems.
- Deploy strategies and how to perform a rollback.
- Configuring environment variables without hardcoding.
What's coming in Module 04
CloudFormation: everything as code. We'll take everything we did manually (DynamoDB tables, Beanstalk applications, IAM policies) and turn it into versionable YAML templates. It will be the most transformative module of the course — after it, you'll never want to click through the console to create resources again.
Deploy is just the beginning. Enjoy the journey — Module 04 is next.
