Deploy OrderFlow to Amazon EC2

Deploy OrderFlow on EC2 with a secured Caddy gateway and Session Manager.

Introduction

30 Second Summary

Something that works perfectly on your computer can become unreachable or overexposed when you put it online. The safest route gives the right person one way in while everything else stays closed.

In this project, you will deploy the inherited OrderFlow stack to Amazon EC2 behind one IP-restricted Caddy gateway. You will administer the host through AWS Systems Manager Session Manager without opening SSH to the internet.

What You'll Build

A checkout sent through your EC2 public address appears in durable Inventory results after an HTTP 202 response.

By the end of this project, you'll have:

  • An Ubuntu admin shell in your browser through Session Manager. Your EC2 host needs no SSH key or port 22 rule.
  • An IP-restricted gateway that answers OrderFlow requests through Caddy. Docker Compose status proves every backend API stays off the host's published ports.
  • A checkout evidence trail beginning with an HTTP 202 response. The client reference ec2-flow-001 appears in /processed-orders. Caddy logs show both requests.
  • Secret Mission: Remove and restore the security group rule for port 8080 to prove AWS reachability can change while OrderFlow stays healthy.

Are there any prerequisites?

You need the completed OrderFlow baseline from Projects 1 through 8 in a GitHub repository that the EC2 host can clone. The repository must contain no credentials or local .env secrets.

Generated database data also stays out of the repository.

Before We Start

This is your moment to commit to deploying the completed OrderFlow Projects 1 through 8 baseline without redesigning its service architecture. You will keep Caddy as the only public application entry point so the backend APIs stay private and the public boundary stays easier to restrict and audit.

Provision a Session Manager Host

OrderFlow needs enough memory to run its inherited ten-container stack on Amazon EC2. Administrative access must stay off the public internet.

You'll use AWS Systems Manager Session Manager as the browser-based access path. SSH port 22 stays closed.

In this step, get ready to:
  • Create the OrderFlowEC2Role instance profile for Session Manager.
  • Restrict gateway traffic to your current public IPv4 address.
  • Reach an Ubuntu Server 24.04 LTS host through Session Manager.
Create the Session Manager role

An AWS Identity and Access Management role gives the EC2 host permission to communicate with Session Manager. The console places this role inside an instance profile that EC2 can attach to the host.

Your inherited GitHub repository already contains the required Compose baseline. It contains no credentials or generated data.

  • In the GitHub repository from earlier, confirm the file list contains compose.yaml, compose.override.yaml, compose.images.yaml, gateway/Caddyfile, plus the three inherited service directories.
  • Navigate to the AWS Management Console in your browser.
  • Sign in to your AWS account.
  • Set the region selector to US East (N. Virginia).
  • Enter IAM in the console search bar.
  • Select IAM from the search results.

Why Session Manager?

SSM Agent initiates an outbound connection to Session Manager. This removes the need for an inbound administrative rule.

Your browser becomes the management path while the host keeps port 22 closed.

  • Choose Roles in IAM.
  • Choose Create role.
  • Select AWS service as the trusted entity.
  • Select the EC2 use case.
  • Search for AmazonSSMManagedInstanceCore in the policy list.
  • Attach AmazonSSMManagedInstanceCore.
  • Enter OrderFlowEC2Role as the role name.
  • Choose Create role to finish.

The role is ready. Its instance profile can now give your EC2 host the permissions required for Session Manager.

Can't Find the Policy?

Check that you are creating an AWS service role for the EC2 use case. Search for the complete policy name AmazonSSMManagedInstanceCore.

Help me find and attach the Session Manager policy to my EC2 role.

Create the restricted network boundary

A security group filters traffic before it reaches the EC2 host. This group allows your current public IPv4 address to reach Caddy on port 8080.

The security group belongs to the same VPC as the host. The host also needs a public subnet with an internet gateway route.

  • Use the VPC console to inspect the route table associated with the subnet you plan to use.
  • Confirm the route table has a 0.0.0.0/0 route to an internet gateway.
  • Record the VPC you checked here: your instance VPC.
  • Record the subnet you checked here: your public subnet.

Why Check the Public Route?

The public IPv4 address needs the internet gateway route for external connectivity. Session Manager also depends on outbound connectivity from the host.

  • Return to the Amazon EC2 console.
  • Use the left navigation to open the security group page.
  • Start creating a security group.
  • Enter orderflow-gateway-sg as the security group name.
  • Select your instance VPC.
  • Add one inbound rule.
  • Set Type to Custom TCP.
  • Set Port range to 8080.

The rule now needs a source. Your current public IPv4 address is the only approved remote client.

  • Set Source to My IP.
  • Keep the default outbound access.
  • Leave port 22 absent from the inbound rules.
  • Finish creating the security group.
  • Review the inbound rules for orderflow-gateway-sg.

You should see one inbound Custom TCP rule for port 8080 from My IP. No inbound rule should allow port 22.

Using a VPN or Changing Networks?

My IP represents the public IPv4 address of your current computer connection. A VPN change or ISP address change can require a new source value later.

Help me verify the inbound boundary for my OrderFlow security group.

Launch the EC2 host

Charges start when this instance begins running. Keeping the host short-lived keeps the project within its small budget.

The host needs enough memory for image builds plus ten containers. The t3.large instance provides 8 GiB of memory for that workload.

  • In Amazon EC2, choose Instances.
  • Start the launch workflow for a new instance.
  • Name the instance orderflow-ec2.
  • Under Application and OS Images (Amazon Machine Image), select Ubuntu Server 24.04 LTS x86_64.
  • Under Instance type, select t3.large.
  • Under Key pair (login), select Proceed without a key pair (not recommended).
  • Under Network settings, select your instance VPC.
  • Choose your public subnet.

Why Proceed Without a Key Pair?

Session Manager is your administrative path. The instance needs no SSH key pair or inbound port 22 rule.

  • Enable Auto-assign Public IP.
  • Select orderflow-gateway-sg as the existing security group.
  • Under Configure storage, set the root volume size to 20 GiB.
  • Set the root volume type to gp3.
  • Keep Delete on termination set to Yes.
  • Expand Advanced details.
  • Set IAM instance profile to OrderFlowEC2Role.
  • Launch the instance.

The host can take several minutes to pass its checks and register with Session Manager. This wait is normal for a new instance.

  • Wait until both EC2 status checks have passed.
  • Select orderflow-ec2 in the instance list.
  • Choose Connect.
  • Select Session Manager.
  • Choose Connect to open the browser shell.

Before you test the shell, make a prediction about the Ubuntu release it reports.

  • Check the host operating system by running this command in the Session Manager shell:
cat /etc/os-release

What Should I See?

The file identifies the operating system installed on the host. You should see Ubuntu 24.04 LTS information in the browser shell.

Session Manager Not Available?

Confirm both EC2 status checks have passed. New instances can need more time before Session Manager becomes available.

Confirm the instance uses OrderFlowEC2Role with AmazonSSMManagedInstanceCore attached. Also confirm the selected subnet has outbound connectivity.

Help me diagnose why Session Manager cannot connect to my Ubuntu EC2 instance.

Your Ubuntu host is live behind a restricted network boundary. Next up, you'll install the remote container toolchain that runs OrderFlow.

Install the Remote Container Toolchain

Your Amazon EC2 host is now reachable through AWS Systems Manager Session Manager without an inbound SSH rule. OrderFlow still cannot run on the host.

The Ubuntu host needs a container toolchain before it can build the inherited application. It also needs the completed source workspace.

In this step, get ready to:
  • Configure Docker's official Ubuntu repository.
  • Install the pinned Docker toolchain.
  • Clone the completed OrderFlow workspace.
Configure Docker's Ubuntu repository

Docker's package repository supplies the pinned Docker Engine packages used by this deployment. The signing key lets Ubuntu verify packages from that source.

  • Switch back to the Session Manager shell from the previous step.
  • Approve the installation if Ubuntu asks for confirmation.
  • Install the repository prerequisites by running this setup:
sudo apt update
sudo apt install ca-certificates curl git
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update

What Did This Setup Add?

  • The prerequisite packages give the host certificate validation plus the download tools needed for the remaining setup.
  • The docker.asc key lets Ubuntu verify packages from Docker.
  • The source definition selects the stable repository for the host's Ubuntu release.
  • The final package update makes Docker's packages available to Ubuntu.

The final update returns you to the shell prompt after reading Docker's package source. The host is ready to install the container toolchain.

Repository Update Failing?

Confirm the instance still has default outbound access. Package downloads also require the public subnet route configured in the previous step.

Check that /etc/apt/keyrings/docker.asc was created before repeating the setup.

Help me troubleshoot Docker repository setup on Ubuntu 24.04.

Install the pinned Docker toolchain

The stack uses Docker Engine to build and run containers. The Docker Compose plugin reads the inherited multi-service model.

Keep using sudo for every Docker command on this short-lived host. This preserves the default protection around the Docker socket.

  • Check the host's current Docker Engine and Compose state by running:
sudo docker version
sudo docker compose version

Choose the Matching Toolchain State

The check separates a correct installation from an older installation or an unavailable command. Choose the tab that matches your shell output.

✔️ I see Engine 29.8.0 and Compose v5.5.0

Good news. The pinned container toolchain is already ready.

  • Continue to the Docker service check below.

ⓧ I see an older version

The existing installation needs the project pins before it can run the inherited stack consistently.

  • Approve the package change if Ubuntu asks for confirmation.
  • Replace the older toolchain with the pinned versions by running:
VERSION_STRING=5:29.8.0-1~ubuntu.24.04~noble
sudo apt install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin
sudo install -m 0755 -d /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/download/v5.5.0/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

What Does This Install?

  • The VERSION_STRING value pins Docker Engine plus the Docker CLI to 29.8.0.
  • The supporting packages provide the container runtime plus Docker Buildx.
  • The system-wide plugin path makes Docker Compose v5.5.0 available through the Docker CLI.

ⓧ Docker is unavailable

This is the expected state for a new host. The installation adds Docker Engine plus the Compose plugin.

  • Approve the package installation if Ubuntu asks for confirmation.
  • Install the pinned container toolchain by running:
VERSION_STRING=5:29.8.0-1~ubuntu.24.04~noble
sudo apt install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin
sudo install -m 0755 -d /usr/local/lib/docker/cli-plugins
sudo curl -SL https://github.com/docker/compose/releases/download/v5.5.0/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

What Does This Install?

  • The VERSION_STRING value pins Docker Engine plus the Docker CLI to 29.8.0.
  • The supporting packages provide the container runtime plus Docker Buildx.
  • The system-wide plugin path makes Docker Compose v5.5.0 available through the Docker CLI.
  • Check that the Docker service is running by executing:
sudo systemctl status docker

What Does This Check?

The status command asks the service manager whether Docker started after installation. A running service can accept container commands.

The status output confirms that Docker is running. The host can now launch containers.

Docker Service Not Running?

Check that the pinned Engine packages finished installing successfully. An interrupted package installation can leave the service unavailable.

Help me troubleshoot the Docker service on Ubuntu 24.04.

Clone the inherited workspace

The container tools provide the runtime. Your completed Git repository provides the OrderFlow services plus their Compose definitions.

Use the working GitHub access method already configured for the repository. Keep credentials out of the clone URL plus the shell history.

  • Record the repository's working HTTPS clone URL here: https://github.com/YOUR-USERNAME/YOUR-REPOSITORY.
  • Record the repository directory name here: YOUR-REPOSITORY.
  • Confirm your existing secure GitHub authentication method can access the repository if it is private.

Keep Credentials Out of Git

Use the secure authentication flow already associated with your private repository. Never place a token inside the clone URL or paste one into shell history.

  • Clone the repository and enter its directory by running this setup:
git clone [[YOUR_REPO_URL="https://github.com/YOUR-USERNAME/YOUR-REPOSITORY"]]
cd [[YOUR_REPOSITORY="YOUR-REPOSITORY"]]
ls compose.yaml compose.override.yaml compose.images.yaml gateway/Caddyfile

What Does This Setup Confirm?

  • The clone command copies the completed OrderFlow source onto the EC2 host.
  • The directory change makes the repository root your active location.
  • The final file check confirms that the three inherited Compose files plus the Caddy configuration are present.

You should see compose.yaml, compose.override.yaml, compose.images.yaml, plus gateway/Caddyfile in the output. Your shell remains inside the repository root.

Clone or File Check Failing?

Confirm that the clone URL points to the completed OrderFlow repository. A private repository also needs the secure access method you already use for that repository.

If a required file is missing, confirm that Projects 1 through 8 were pushed to the selected repository.

Help me diagnose my OrderFlow repository clone.

Before you run the final check, what should the test container prove about the Docker service?

  • Verify the remote toolchain from the cloned repository by running:
sudo docker run hello-world && sudo docker version && sudo docker compose version

What Does the Final Check Prove?

  • The test container proves that Docker can retrieve an image and run a container.
  • The Engine output identifies Docker Engine 29.8.0.
  • The Compose output identifies Docker Compose v5.5.0.

That is the remote toolchain working. The test container prints its confirmation before Docker reports the Engine plus Compose versions.

Final Toolchain Check Failing?

A failed image download usually points to missing outbound connectivity. Recheck the instance's public subnet route plus the security group's default outbound access.

A Docker permission failure means the command was run without sudo. Keep the prefix on every Docker command in this project.

Help me fix the remote Docker verification.

Your EC2 host can now build containers from the completed OrderFlow workspace. Next, you will deploy the inherited stack with its loopback-only gateway binding.

Deploy the Loopback-Only Stack

Your Amazon EC2 host now has the container toolchain and completed OrderFlow source it needs. The stack is ready for its first cloud run.

The inherited Docker Compose files were designed for local runs. This deployment keeps them unchanged so you can test Caddy from inside and outside the host.

In this step, get ready to:
  • Render the inherited ten-service Compose model.
  • Bring the inherited OrderFlow stack to a healthy running state.
  • Compare host-local and remote gateway reachability.
Validate the inherited model

Docker Compose merges the three inherited files in command-line order. Rendering the service list confirms that this host has the completed Project 8 model before any containers start.

  • Render the inherited service list by running this command in the Session Manager shell:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml config --services

What does this command check?

  • Each -f option adds one inherited Compose file to the model.
  • The files merge in the order shown by the command.
  • The config --services output lists the services produced by that merged model.

You should see ten service names in the output. The list includes caddy plus the completed OrderFlow APIs, workers, and infrastructure services.

Good progress. The EC2 host is reading the same ten-service model you completed locally.

Missing services from the list?

  • Confirm that your Session Manager prompt is still inside the cloned OrderFlow repository.
  • Check that compose.yaml, compose.override.yaml, and compose.images.yaml remain in that directory.
  • Compare the file names in the command with the files shown by your earlier repository check.

Ask for help with the rendered model: troubleshoot my missing OrderFlow Compose services.

Build and start OrderFlow

The merged model now needs to become a running application. The first build can take a few minutes while Docker creates images and starts the database containers.

  • Build the stack and print its container status by running these commands:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml up --build --wait
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml ps

What happens during startup?

  • The up command creates the inherited containers and networks.
  • The --build flag builds the application images before startup.
  • The --wait flag waits for the services to reach their running or healthy states.
  • The ps command displays each container's current status and published ports.

You should see Caddy and the three APIs marked as healthy. The workers and infrastructure services should appear as running or healthy.

That is the first cloud deployment running. All ten OrderFlow services now share the EC2 host.

Did a container fail to start?

  • Read the failing service name from the Compose output.
  • Check whether the build output reports a missing inherited file.
  • Confirm that the EC2 instance still has outbound access for image downloads.

Get help with the failed container: diagnose my OrderFlow Compose startup failure.

Compare local and remote reachability

A host-local request checks whether OrderFlow works inside the EC2 instance. A remote request checks whether the approved Linux computer can reach the same gateway through the instance's public address.

Before you run the host-local check, do you expect the seeded product to be available from inside EC2?

  • Request the seeded product from inside the EC2 host by running:
curl --include http://127.0.0.1:8080/products/TEE-001

What does this request prove?

  • The --include option displays the HTTP response headers with the response body.
  • The address 127.0.0.1 sends the request through the EC2 host's loopback interface.
  • The path /products/TEE-001 asks Catalog for the inherited seeded product.

You should see a successful HTTP response containing product data for TEE-001. This proves the gateway and private backend path work inside the host.

No seeded product response?

  • Confirm that the Compose status still shows Caddy and the APIs as healthy.
  • Check that the request uses port 8080 on 127.0.0.1.
  • Verify that the product path ends with TEE-001.

Ask for help with the local request: troubleshoot my host-local OrderFlow product request.

  • Switch back to the AWS Management Console from earlier.
  • Select orderflow-ec2 from Instances.

The instance details show its current Public IPv4 address. This is the address your Linux computer uses for the remote check.

  • Record the displayed address here: replace-with-ec2-public-ipv4.
  • Open your Linux computer's application launcher.
  • Select your terminal application.

Before you run the remote check, do you think the security group rule alone is enough to make Caddy reachable?

  • Set the instance address and test remote reachability by running these commands on your Linux computer:
PUBLIC_IP="[[PUBLIC_IP="replace-with-ec2-public-ipv4"]]"
curl --max-time 3 --include "http://$PUBLIC_IP:8080/products/TEE-001"

This failure is intentional

You should see the request time out or fail to connect after three seconds. This is the designed obstacle for the deployment.

The security group admits your public IPv4 address to port 8080. Docker still publishes Caddy only on the EC2 loopback interface.

That contrast proves the stack is healthy while remote traffic remains blocked by the inherited Docker binding.

Did the remote request succeed?

  • Confirm that you ran the remote request on your Linux computer.
  • Check that the running stack was created from only the three inherited Compose files.
  • Confirm that Caddy's published port remains bound to the EC2 loopback address.

Ask for help with the unexpected result: explain why my loopback-only OrderFlow gateway is remotely reachable.

You proved that OrderFlow is healthy on EC2 while its inherited gateway binding blocks remote access. Next, you'll change only Caddy's published port and test the approved path again.

Publish Only the Caddy Gateway

Your host-local test proved that OrderFlow is healthy on Amazon EC2. The failed remote request exposed the remaining boundary. The AWS security group permits port 8080, while Docker keeps Caddy attached to the instance loopback address.

A small Docker Compose overlay changes only Caddy's published port. The backend APIs stay private while your approved client reaches the gateway through the security group.

In this step, get ready to:
  • Define the EC2-specific Caddy port override.
  • Validate the merged four-file Compose model.
  • Apply the four-file model to expose only Caddy.
Create the EC2-specific override

Compose loads override files in command-line order. The final file can replace Caddy's inherited loopback mapping without changing the local Compose files.

  • In the Session Manager shell from earlier, create compose.ec2.yaml inside the active cloned repository with your terminal editor.
  • Paste this configuration into compose.ec2.yaml:
services:
  caddy:
    ports: !override
      - "8080:8080"

What Does This Configuration Do?

  • The services key targets service definitions in the merged Compose model.
  • The caddy key limits this override to the gateway service.
  • The ports: !override entry replaces the inherited port list. This removes the 127.0.0.1:8080:8080 mapping.
  • The "8080:8080" mapping publishes the container port on all host addresses. The security group still restricts remote access to My IP.
  • Save compose.ec2.yaml.

✔️ Awesome, I've got everything!

Great. Double check that compose.ec2.yaml is saved in the active cloned repository.

ⓧ I'd like to double check the full code

Compare your complete compose.ec2.yaml file with this reference:

services:
  caddy:
    ports: !override
      - "8080:8080"

Does the Override Look Different?

Check the YAML indentation. The caddy key sits under services. The ports key sits under caddy.

Make sure the mapping contains 8080:8080 without 127.0.0.1.

Help me check my Compose override.

Validate the four-file model

The overlay must be loaded last because later Compose files take precedence. Rendering the combined model lets you inspect the final boundary before changing the running stack.

  • Render the four-file Compose model in the Session Manager shell by running:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml -f compose.ec2.yaml config

What Does This Command Prove?

  • The command merges the four Compose files in the order shown.
  • The final rendered output shows the configuration Docker applies to the running stack.
  • Loading compose.ec2.yaml last gives its Caddy port replacement precedence.
  • Locate the rendered caddy service in the output.
  • Confirm it has one published mapping from host port 8080 to container port 8080.
  • Confirm the Order, Inventory, and Catalog services have no host-published ports.

That is the critical boundary confirmed. Caddy is the only application service prepared for host-level access. The backend APIs remain private inside the Compose network.

Still Seeing the Loopback Mapping?

Confirm compose.ec2.yaml is the final file in the command. An earlier position allows a later file to replace its value.

If a backend service shows a published port, compare the rendered service with the inherited files before continuing.

Help me inspect my merged Compose model.

Apply the cloud binding

Applying the merged model reconciles the running stack with the new Caddy mapping. The --wait flag keeps the command active until the services are running or healthy.

  • Rebuild the running stack from the four-file model in the Session Manager shell by running:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml -f compose.ec2.yaml up --build --wait

What Happens During This Update?

The --build flag rebuilds the required images before starting the services. The --wait flag waits for the running or healthy state.

Compose applies compose.ec2.yaml last. This replaces only Caddy's published-port definition.

The command returns after the stack reaches its running or healthy state.

  • Inspect the running services and published ports by running:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml -f compose.ec2.yaml ps

What Should the Status Confirm?

The status output shows each service state. Its ports column reveals which services publish ports on the EC2 host.

You should see the ten-service stack running. Only Caddy should show a published application port.

Order, Inventory, and Catalog should show no published host ports.

Before you test from your Linux computer, do you expect the same public request to time out again?

  • Return to the Linux terminal from the earlier failed request.
  • Test the gateway, product, and orders paths through the EC2 public IPv4 address by running:
curl --include "http://$PUBLIC_IP:8080/gateway-health"
curl --include "http://$PUBLIC_IP:8080/products/TEE-001"
curl --include "http://$PUBLIC_IP:8080/orders"

What Do These Checks Prove?

  • The /gateway-health response proves your approved client can reach Caddy through the EC2 public address.
  • The /products/TEE-001 response proves Caddy can route the remote request to Catalog.
  • The /orders response proves the Order route is reachable through Caddy.

This time, all three requests return HTTP responses instead of timing out. The product response includes the seeded TEE-001 item.

You have closed the network gap. Your approved client now reaches OrderFlow through one Caddy gateway without publishing the backend APIs.

Are the Remote Requests Timing Out?

Confirm PUBLIC_IP still contains the EC2 public IPv4 address. Check that the security group still has one Custom TCP port 8080 rule from My IP.

Your public IPv4 address can change when you switch networks or use a VPN. Update the My IP rule if your current address has changed.

Help me diagnose my EC2 gateway timeout.

Your approved client can now reach OrderFlow through one controlled gateway. Next, you will send a real checkout through Caddy and trace its durable result.

Prove the Remote Checkout Flow

Your EC2 gateway now serves the seeded product through Caddy. Your approved public address can reach the application through port 8080.

Basic reachability proves only that the gateway answers. A real checkout must continue beyond that gateway.

Order accepts the checkout after Caddy forwards it. RabbitMQ carries the message to Inventory for durable processing.

In this step, get ready to:
  • Submit a checkout through the public gateway.
  • Retrieve the durable Inventory outcome.
  • Trace both requests in the Caddy access logs.
Submit a checkout from your Linux computer

The client_reference value identifies one checkout across the asynchronous flow. You will use ec2-flow-001 to follow this request from submission to storage.

Before you run the request, predict which status code confirms that Order accepted the checkout.

  • Return to the terminal on your Linux computer from the previous step.
  • Use your saved public address to send ec2-flow-001 by running:
curl --include \
  --header "Content-Type: application/json" \
  --data '{"client_reference":"ec2-flow-001","sku":"SCALE-001","quantity":1}' \
  "http://$PUBLIC_IP:8080/orders"

What Does This Request Prove?

  • The --include flag prints the HTTP response headers. This makes the status code visible.
  • The Content-Type: application/json header tells Order how to interpret the request body.
  • The JSON body submits one unit of the seeded SCALE-001 product under the reference ec2-flow-001.
  • The public URL sends the checkout through Caddy before it reaches Order.

You should see HTTP 202 in the response headers. That response is the key milestone: Order has accepted your remote checkout for asynchronous processing.

Checkout Not Accepted?

  • Confirm that PUBLIC_IP still contains the public IPv4 address for orderflow-ec2.
  • Check that the JSON body keeps double quotes around every field name.
  • Confirm that the security group still allows port 8080 from your current public IPv4 address.

Help me diagnose why my OrderFlow checkout request does not return HTTP 202.

Retrieve the asynchronous outcome

An HTTP 202 response confirms acceptance. The durable result appears after Inventory processes the queued message.

Before you check the result, predict which client reference should appear in the processed orders.

  • Request the durable Inventory results through the public gateway by running:
curl --include "http://$PUBLIC_IP:8080/processed-orders"

What Does This Result Prove?

The /processed-orders route reads the durable outcomes produced by Inventory. Finding the same client reference proves that the message continued beyond the initial Order response.

You should find ec2-flow-001 in the processed orders. You have now proved that the remote checkout reached durable Inventory processing.

Can't Find the Client Reference?

  • Wait briefly for Inventory to process the queued message.
  • Run the processed-orders request again.
  • Confirm that the checkout response included HTTP 202.

Help me trace why ec2-flow-001 is missing from the processed OrderFlow results.

Trace the gateway requests in Caddy

Access logs show which requests crossed the public gateway. Docker Compose can read recent output from only the Caddy service.

Before you inspect the logs, predict which two request paths should appear.

  • Switch back to the existing Session Manager shell.
  • Read the recent Caddy output by running:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml -f compose.ec2.yaml logs --since 5m caddy

What Does This Command Show?

  • The logs command reads output from the running Compose application.
  • The --since 5m option limits the output to recent requests.
  • The caddy service name keeps the evidence focused on the public gateway.

You should see access records for /orders and /processed-orders. Those records complete your end-to-end proof that both remote requests crossed Caddy.

Missing a Request Path?

  • Rerun the missing request from your Linux computer.
  • Repeat the log command within five minutes.
  • Confirm that the command targets the caddy service.

Help me find the OrderFlow checkout requests in my Caddy logs.

Secret mission

Prove the Security Group Boundary

Your gateway is healthy and publicly reachable. Temporarily remove its AWS security-group rule to prove remote traffic stops while OrderFlow stays healthy on the EC2 host. Restore the rule without restarting Docker Compose.

Clean Up Your Resources

Clean Up Your Resources

Your Amazon EC2 deployment is still live in us-east-1. You can keep it running, pause the instance for later, or delete the cloud resources entirely.

Cost warning

The running t3.large instance costs $0.0835 per hour. Its public IPv4 address costs $0.005 per hour.

The 20 GiB gp3 root volume costs $0.08/GB-month. Together, the base deployment costs about $0.09 per hour while running. Sustained T3 CPU bursting or internet data transfer can add small charges.

Stop the instance if you plan to return soon. Terminate it promptly if you have finished testing OrderFlow.

Resources you used:

  • One orderflow-ec2 t3.large instance with one public IPv4 address.
  • One 20 GiB gp3 Amazon EBS root volume with Delete on termination set to Yes.
  • One security group named orderflow-gateway-sg.
  • One AWS Identity and Access Management role named OrderFlowEC2Role attached through an instance profile.
  • The Docker images, containers, networks, and volumes stored on the EC2 root volume.
  • The cloned OrderFlow workspace with compose.yaml, compose.override.yaml, compose.images.yaml, and compose.ec2.yaml.

Keep everything running

No action is needed. Choose this if you are still testing the remote checkout flow or collecting deployment evidence.

  • Leave orderflow-ec2 running while you continue testing OrderFlow.
  • Keep the Custom TCP port 8080 rule restricted to My IP.
  • Budget about $0.09 per hour before possible T3 surplus CPU-credit charges or data transfer.

Pause - I'll come back to this later

Stop the instance to pause its compute charges while preserving the host. The EBS root volume continues to incur storage charges.

  • Return to the EC2 console from earlier.
  • Select Instances.
  • Select orderflow-ec2.
  • Choose Instance state.
  • Choose Stop instance.
  • Choose Stop to confirm.

A later start normally assigns a new public IPv4 address. Your current PUBLIC_IP value may no longer reach the gateway.

  • Update PUBLIC_IP with the new public IPv4 address after restarting the instance.
  • Update the My IP source if your Linux computer has a different public IPv4 address.

Delete - I don't want to use this again

Removing the host permanently deletes its local clone and container data. Your GitHub repository remains your persistent source copy.

  • Stop the running OrderFlow application cleanly from the existing Session Manager shell by running this command:
sudo docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml -f compose.ec2.yaml down

What does this command do?

Docker Compose removes the OrderFlow containers and networks created by the four-file model. Named volumes remain because the command does not include --volumes.

Terminating orderflow-ec2 removes the remaining container data when AWS deletes the root volume.

  • Return to the EC2 console from earlier.
  • Select Instances.
  • Select orderflow-ec2.
  • Choose Instance state.
  • Choose Terminate (delete) instance.
  • Choose Terminate (delete) to confirm.

Termination can take a few minutes while AWS releases the instance and its attached network interface.

  • Wait until orderflow-ec2 reaches terminated.
  • Check the EC2 volume list to confirm the 20 GiB root volume is gone.

The instance termination removes the remote OrderFlow clone and every Docker resource stored on that root volume. The remaining standalone AWS resources need their own cleanup.

  • Go to the security group list in the EC2 console.
  • Delete orderflow-gateway-sg after the terminated instance releases its network interface.
  • Return to IAM from the AWS Management Console.
  • Choose Roles.
  • Delete OrderFlowEC2Role from the role list.

Security group still attached?

AWS can briefly retain the terminated instance's network interface. Wait a few minutes before retrying the security group deletion.

If the security group still cannot be removed, ask for help checking which AWS resource still references it.

That closes the loop. The short-lived OrderFlow cloud deployment no longer creates instance, public IPv4, or root-volume charges.

Nice Work!

Nice Work!

You did it! OrderFlow now runs as a ten-service stack on an IP-restricted Amazon EC2 host. You proved that Caddy can be the sole public application gateway while every backend stays private.

You've learned how to:

  • Provision a right-sized Ubuntu host with a restricted network boundary. Use Session Manager for browser-based administration without exposing SSH.
  • Deploy the inherited stack with Docker Compose. Add compose.ec2.yaml to publish only Caddy while Order, Inventory, and Catalog remain private.
  • Send a remote asynchronous checkout through Caddy. Confirm HTTP 202 before finding ec2-flow-001 in the durable Inventory results. Trace both gateway requests in the Caddy logs.
  • Secret Mission: Prove the security group boundary by removing the port 8080 rule. Compare failed remote access with successful host-local health. Restore the approved rule without restarting Docker Compose.

Ready to quiz yourself?