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

  1. What Beanstalk does for you
  2. Anatomy of a Beanstalk application
  3. Environments: dev, staging, prod
  4. Preparing the Orders service
  5. Configuring IAM for Beanstalk
  6. First deploy
  7. Health checks and auto-recovery
  8. Logs and troubleshooting
  9. Updating the service (continuous deployment)
  10. Environment variables and configuration
  11. Practice exercises
  12. 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

TypeWhen to useComponents
Web ServerAPIs and HTTP applicationsEC2 + ALB + ASG
WorkerBackground task processing via SQSEC2 + 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 similar

05. 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

  1. Go to IAM > Policies > Create policy.
  2. Use the JSON editor and paste the policy below.
  3. Replace 123456789012 with your account ID.
  4. Policy name: orders-service-dynamodb-access.
  5. In IAM > Roles, find aws-elasticbeanstalk-ec2-role.
  6. 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 | head

Creating the application in the console

  1. AWS Console > Elastic Beanstalk.
  2. Confirm region us-east-1.
  3. Click Create application.
  4. Application name: orders-service.
  5. Tags: Project=order-system, Environment=dev.
  6. Platform: Node.js 20 running on 64bit Amazon Linux 2023.
  7. Application code: Upload your code > select orders-v1.zip.
  8. Version label: v1.0.0.
  9. Presets: choose Single instance (Free Tier eligible).
  10. Click Next to continue with the detailed configuration.

Detailed configuration (important)

  1. Service role: use the default aws-elasticbeanstalk-service-role.
  2. EC2 instance profile: use aws-elasticbeanstalk-ec2-role (with the DynamoDB policy we created).
  3. Instance type: t2.micro (Free Tier).
  4. Instance metadata service: version v2 (more secure).
  5. Root volume type: General Purpose (SSD), 8 GB.
  6. Environment name: orders-dev.
  7. Environment properties (environment variables): we'll define these in section 10.
  8. 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

TypeWhat it checksFrequency
BasicOnly whether the EC2 is respondingEvery 30s
EnhancedEC2 + ALB + metrics + app logsEvery 10s

Always use Enhanced. It's free (included in the Free Tier) and gives you far superior visibility.

Health states

StateMeaning
OkEverything working normally
WarningSome issue detected, but not critical
DegradedDegraded performance (e.g., too many 5xx)
SevereSerious problem — possibly down
PendingChange in progress (deploy, scaling)
UnknownBeanstalk 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

  1. Environment > Configuration > Software > Edit.
  2. Under CloudWatch Logs, check Log streaming.
  3. Retention: 7 days (enough for the study).
  4. Lifecycle: Keep logs after terminating environment? No.
  5. Save.

The log files that matter

FileWhat it contains
nodejs/nodejs.logYour application's stdout/stderr
nginx/access.logIncoming HTTP requests
nginx/error.lognginx errors (reverse proxy)
eb-engine.logBeanstalk deploy logs
eb-hooks.logPre/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

  1. Make the changes in your local code.
  2. Create a new zip: orders-v1.1.zip.
  3. Environment > Upload and deploy.
  4. Select the new zip.
  5. Version label: v1.1.0 (always increment).
  6. Click Deploy.

Deploy strategies

StrategyHow it worksRisk
All at onceUpdates all instances at the same timeDowntime during the deploy
RollingUpdates in batchesTemporarily reduced capacity
Rolling with batchLaunches new ones, then removes old onesExtra cost during deploy
ImmutableCreates a parallel ASG, swaps after validationSlower, 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:

  1. Environment > Application Versions.
  2. Find the previous stable version.
  3. 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

  1. Environment > Configuration > Updates, monitoring, and logging > Edit.
  2. Go to the Environment properties section.
  3. Add key-value pairs.
  4. 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

Exercício
01

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.

Exercício
02

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.

Exercício
03

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.

Exercício
04

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.

Exercício
05

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.