Deploy a Dockerized App with Terraform
Provision an EC2 instance with Terraform and deploy a containerized Node.js app.
Introduction
30 Second Summary
You wrote an app and it works on your laptop. Getting it running on a server that the whole internet can reach is a different story. That gap between "works locally" and "works in production" is where most developers get stuck.
In this project, you will use Terraform and Docker to deploy a web application to AWS. You will write one configuration file, run one command, and have a live server with your app running in a container that anyone can visit from their browser.
What You'll Build
By the end, you will type a public IP address into your browser and see a web page served from a Docker container running on an AWS server that you provisioned entirely through code.
By the end of this project, you'll have:
- A Terraform configuration that creates a cloud server, SSH key pair, and security group with a single terraform apply command.
- A Docker container running a custom Node.js web server that anyone on the internet can access through a public IP address.
- The hands-on experience of diagnosing a blocked port and fixing it by updating your Terraform code, proving the power of Infrastructure as Code.
- Secret Mission: Add environment variables and a Docker health check to make your container production-ready.
Are there any prerequisites?
You need an AWS account created after July 15, 2025 for free tier eligibility. No prior experience with Terraform, Docker, or the AWS CLI is required.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters. This step is your chance to commit to the goal before any hands-on work begins.
Install Your DevOps Toolkit
To deploy infrastructure to AWS, your local machine needs two tools. Terraform turns configuration files into real cloud resources. The AWS CLI lets your terminal communicate with AWS services directly.
In this step, you will install both tools on Windows and configure the AWS CLI with your credentials. Then you will create a project folder where all your Terraform files will live.
In this step, get ready to:
- Install Terraform and add it to your system PATH.
- Install and configure the AWS CLI with your credentials.
- Create and open a project folder in VS Code.
Install Terraform
Terraform is an Infrastructure as Code tool that reads .tf configuration files and creates matching cloud resources. You need it installed locally before you can provision anything on AWS.
- Create a folder to store the Terraform binary by running this command in PowerShell:
- Open PowerShell by pressing the Windows key, typing PowerShell, and pressing Enter.
mkdir C:\terraform
- Open the Terraform download page in your browser:
https://developer.hashicorp.com/terraform/install
- Click the Windows tab, then download the AMD64 zip file.
- Open the downloaded zip file and extract terraform.exe into the C:\terraform folder you just created.
Now you need to add C:\terraform to your system PATH so Windows can find the terraform command from any terminal.
- Press the Windows key, type environment variables, and select Edit the system environment variables.
- Click Environment Variables at the bottom of the System Properties window.
- Under System variables, find and select Path, then click Edit.
- Click New and type C:\terraform.
- Click OK three times to close all dialogs.
What is the system PATH?
The PATH is a list of folders Windows searches when you type a command. By adding C:\terraform to this list, you can run terraform from any directory without typing the full file path.
- Close your current PowerShell window and open a new one (press the Windows key, type PowerShell, press Enter). The PATH update only takes effect in new terminal sessions.
Before you run the next command, what do you expect to see if the install worked?
- Verify Terraform is installed by running:
terraform version
✔️ I see a version number
You should see output like Terraform v1.15.8 (your exact version may differ). Terraform is ready to use.
ⓧ Command not found
If PowerShell cannot find terraform, double check these items:
- Make sure you opened a brand new PowerShell window after editing the PATH. Existing windows do not pick up PATH changes.
- Confirm that terraform.exe is inside C:\terraform. Run dir C:\terraform to check.
- Re-open the Environment Variables editor and verify that C:\terraform appears in the Path list under System variables.
Install and configure the AWS CLI
The AWS CLI is a command-line tool that lets you create and manage AWS resources directly from your terminal. You will use it alongside Terraform to authenticate with your AWS account.
- Download the AWS CLI installer by opening this link in your browser:
https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file and follow the installer prompts. Accept the default options and click through to finish.
- Close your PowerShell window and open a new one so it picks up the new installation.
What do you expect to see when you check the AWS CLI version?
- Verify the installation by running:
aws --version
✔️ I see a version number
You should see output like aws-cli/2.27.41 Python/3.11.6 Windows/10 exe/AMD64 prompt/off. The exact numbers may differ. The AWS CLI is ready.
ⓧ Command not found
If PowerShell cannot find aws, try these fixes:
- Make sure the MSI installer completed without errors.
- Close ALL PowerShell windows and open a brand new one. The installer adds the AWS CLI to your PATH automatically, but only new sessions see the change.
- If it still does not work, restart your computer and try again.
Now you need to give the AWS CLI permission to access your AWS account. This requires an Access Key ID and Secret Access Key from IAM.
Where do I get my access keys?
You create access keys in the AWS Management Console. Sign in, then click your account name in the top-right corner and select Security credentials.
Scroll down to Access keys and click Create access key. Select Command Line Interface (CLI) as the use case, acknowledge the recommendation, and click Next. Then click Create access key.
Copy both the Access Key ID and Secret Access Key now. The secret key is only shown once. Store them in a safe place.
- Configure the AWS CLI with your credentials by running:
aws configure
What will this command ask me?
The aws configure command prompts you for four values one at a time. Enter them exactly as shown:
- AWS Access Key ID: paste your access key ID.
- AWS Secret Access Key: paste your secret access key.
- Default region name: type us-east-1.
- Default output format: type json.
- Enter each value when prompted and press Enter after each one.
Seeing an error during aws configure?
Make sure you paste the full Access Key ID and Secret Access Key with no extra spaces. If you closed the key creation page and lost the secret key, go back to the IAM console, delete the old key, and create a new one.
help me fix my aws configure setup
Create a project folder
You need a dedicated folder for your Terraform configuration files. You will create it on your Desktop so it is easy to find.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir docker-on-aws
cd docker-on-aws
- Confirm the folder exists by running:
pwd
You should see a path ending in docker-on-aws.
- Open this folder in VS Code by running:
code .
What does code . do?
The code . command opens VS Code with the current folder as the workspace. The dot means "this folder". VS Code should open with docker-on-aws shown in the Explorer sidebar.
- In VS Code, open the integrated terminal by pressing Ctrl+` (backtick key, above Tab).
- Confirm the terminal opens inside your docker-on-aws folder. The prompt should show the folder name.
VS Code didn't open?
If code . is not recognized, open VS Code manually (press the Windows key, type VS Code, press Enter). Then click File in the top menu, select Open Folder, and navigate to Desktop > docker-on-aws.
help me open my project folder in VS Code
Your DevOps toolkit is ready. You have Terraform for building infrastructure, the AWS CLI for authentication, and a project folder open in VS Code. Next up, you will write your first Terraform configuration that defines an entire cloud server.
Write Your Terraform Configuration
Your DevOps toolkit is installed and configured. Now it is time to tell Terraform what to build.
Terraform needs a configuration file that describes exactly what infrastructure to create. Without this file, Terraform has nothing to build. You are defining your entire cloud environment as code.
In this step, get ready to:
- Write a Terraform config that declares the AWS provider and finds the right server image.
- Add resources for an SSH key pair, a security group, and an EC2 instance with Docker pre-installed.
- Create an outputs file so Terraform reports your connection details after deployment.
Create main.tf with provider and AMI lookup
Every Terraform project starts with a provider block. Providers are plugins that let Terraform talk to specific cloud platforms. You also need a data source to find the correct Amazon Machine Image (AMI) for your server.
- In the VS Code Explorer sidebar, right-click inside the docker-on-aws folder and select New File.
- Name the file main.tf and press Enter.
- Add the provider configuration and AMI lookup by pasting this code into main.tf:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-kernel-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
What does this code do?
- The terraform block declares two required providers: aws (to manage AWS resources) and tls (to generate SSH keys). The ~> operator allows minor version updates within the major version.
- The provider "aws" block sets the region to us-east-1 so all resources are created there.
- The data "aws_ami" block searches AWS for the most recent Amazon Linux 2023 AMI. Terraform uses this image ID when creating your server.
- Save main.tf.
- Initialize the Terraform project by running this command in the integrated terminal:
Before you run this, what do you think Terraform needs to download before it can manage AWS resources?
terraform init
Terraform downloads the AWS and TLS provider plugins and prints Terraform has been successfully initialized! at the end of the output.
Seeing an error during init?
- If you see a provider registry error, check that you have internet access and that the source values in your terraform block match hashicorp/aws and hashicorp/tls exactly.
- If you see No configuration files, make sure your terminal is inside the docker-on-aws folder where main.tf lives.
Why is terraform init failing for me?
Add the SSH key, security group, and EC2 instance
Now you will define the actual resources Terraform creates in AWS. These three pieces work together: the SSH key lets you connect, the security group controls network access, and the EC2 instance is your server.
- In main.tf, add the SSH key pair resources below the data "aws_ami" block:
resource "tls_private_key" "ssh_key" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "deployer" {
key_name = "docker-project-key"
public_key = tls_private_key.ssh_key.public_key_openssh
}
What does this code do?
The tls_private_key resource generates a 4096-bit RSA key pair entirely within Terraform. The aws_key_pair resource registers the public half of that key with AWS so your EC2 instance trusts it for SSH connections.
- Below the aws_key_pair block, add the security group:
resource "aws_security_group" "web_server" {
name = "docker-project-sg"
description = "Security group for Docker project"
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
What does this code do?
A security group acts as a virtual firewall for your instance. This one allows inbound SSH traffic on port 22 (so you can connect) and allows all outbound traffic (so the instance can download packages). Notice there is no rule for port 80 yet. That omission is intentional.
- Save main.tf and confirm your configuration is still valid by running:
terraform validate
You should see Success! The configuration is valid. confirming no syntax errors so far.
Seeing a validation error?
- Check for missing closing braces. Each resource block and each nested ingress/egress block must have its own closing }.
- Confirm that cidr_blocks uses square brackets (it is a list): ["0.0.0.0/0"].
Why is terraform validate failing on my security group?
- Below the security group, add the EC2 instance resource:
resource "aws_instance" "docker_host" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.small"
key_name = aws_key_pair.deployer.key_name
vpc_security_group_ids = [aws_security_group.web_server.id]
associate_public_ip_address = true
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y docker
systemctl start docker
systemctl enable docker
usermod -a -G docker ec2-user
EOF
tags = {
Name = "docker-project-host"
}
}
What does this code do?
- The ami field references the Amazon Linux 2023 image you looked up earlier with the data source.
- The instance_type is t3.small (2 vCPUs, 2 GiB RAM). This is covered by the AWS Free Tier for accounts created after July 15, 2025.
- The user_data script runs automatically when the instance first boots. It installs Docker and adds the ec2-user to the docker group so you can run containers without sudo.
- Save main.tf and validate the full configuration by running:
terraform validate
You should see Success! The configuration is valid. again. All four resources reference each other correctly.
Validation fails after adding the instance?
- A reference error like Reference to undeclared resource usually means a typo in a resource name. Confirm you have aws_key_pair.deployer and aws_security_group.web_server spelled exactly as shown.
- Make sure the user_data heredoc ends with EOF on its own line with no leading spaces before it.
Why is my EC2 instance resource failing validation?
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf and move on to the next substep.
ⓧ I'd like to double check the full code
Your main.tf should contain the following sections in order. Scroll up and confirm each one is present:
- The terraform block with required_providers (aws and tls).
- The provider "aws" block with region us-east-1.
- The data "aws_ami" "amazon_linux" block with two filters.
- The resource "tls_private_key" "ssh_key" block.
- The resource "aws_key_pair" "deployer" block.
- The resource "aws_security_group" "web_server" block with one ingress (port 22) and one egress (all).
- The resource "aws_instance" "docker_host" block with user_data that installs Docker.
Create outputs.tf
After Terraform creates your instance, you need a way to retrieve the public IP address and SSH key. Outputs expose these values in the terminal when deployment finishes.
- In the VS Code Explorer sidebar, right-click inside the docker-on-aws folder and select New File.
- Name the file outputs.tf and press Enter.
- Add the output definitions by pasting this code into outputs.tf:
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = aws_instance.docker_host.public_ip
}
output "ssh_private_key" {
description = "Private SSH key to connect to the instance"
value = tls_private_key.ssh_key.private_key_pem
sensitive = true
}
What does this code do?
- The instance_public_ip output prints the server's public IP after deployment so you know where to connect.
- The ssh_private_key output holds the private key you will use for SSH. It is marked sensitive = true so Terraform hides it from normal output. You will extract it with a special command later.
- Save outputs.tf.
Before you validate, do you think Terraform will accept these outputs? Both reference resources defined in a different file (main.tf). Does Terraform care which file a resource is defined in?
- Run the final validation to confirm both files work together:
terraform validate
You should see Success! The configuration is valid. one more time. Terraform treats all .tf files in the same folder as one configuration, so cross-file references work automatically.
✔️ Awesome, I've got everything!
Great. Double check you have saved outputs.tf and you are ready to deploy.
ⓧ I'd like to double check the full code
output "instance_public_ip" {
description = "Public IP address of the EC2 instance"
value = aws_instance.docker_host.public_ip
}
output "ssh_private_key" {
description = "Private SSH key to connect to the instance"
value = tls_private_key.ssh_key.private_key_pem
sensitive = true
}
Validation error in outputs.tf?
- Make sure the resource names match exactly: aws_instance.docker_host and tls_private_key.ssh_key. A single character difference causes a reference error.
- Confirm that outputs.tf is saved inside the same docker-on-aws folder as main.tf. Terraform only reads .tf files in its working directory.
Why is my outputs.tf failing terraform validate?
Your infrastructure is fully defined as code. Two files, zero clicks in the AWS console. Next up, you will run terraform apply and watch Terraform bring this configuration to life as real cloud resources.
Deploy Your Infrastructure and Connect
Your Terraform configuration is complete, but it's still just a file on your computer. Nothing exists in AWS yet.
In this step, you will run the commands that turn your configuration into real cloud infrastructure. You will watch Terraform create resources in your AWS account and then connect to your new server over SSH.
In this step, get ready to:
- Preview and apply your Terraform configuration to create AWS resources.
- Save your SSH private key and fix its file permissions.
- SSH into your EC2 instance and confirm Docker is running.
Plan and apply your infrastructure
Before Terraform creates anything, it can show you exactly what it plans to do. This preview step lets you catch mistakes before they cost time or money.
- Preview what Terraform will create by running this command in your VS Code terminal:
terraform plan
Terraform reads your main.tf and outputs.tf files, compares them against what exists in AWS (nothing yet), and prints a summary of what it will create.
At the bottom of the output, you should see a line like: Plan: 4 to add, 0 to change, 0 to destroy.
What does the plan show?
The + symbol next to each resource means Terraform plans to create it. Your 4 resources are the TLS private key, the AWS key pair, the security group, and the EC2 instance.
Values marked (known after apply) are things AWS assigns at creation time, like the instance ID and public IP address.
The plan looks good. Before you run the next command, what do you think will happen when you tell Terraform to apply this plan?
- Create the resources in AWS by running:
terraform apply
What happens during apply?
Terraform shows the same plan again and asks you to confirm. It will prompt Do you want to perform these actions? and wait for your input.
- Type yes and press Enter to confirm.
Terraform now creates each resource in order, respecting dependencies. This takes about 30-60 seconds.
When it finishes, you should see Apply complete! Resources: 4 added, 0 changed, 0 destroyed. followed by the instance_public_ip output showing your server's IP address.
Seeing an authentication error?
If Terraform prints an error about credentials or authorization, your AWS CLI configuration from Step 1 may be incorrect. Run aws configure again and double-check your Access Key ID, Secret Access Key, and region (us-east-1).
Help me fix my Terraform authentication error.
Save your SSH key and connect to the instance
Terraform generated an SSH key pair during apply. The public key is registered with AWS, but the private key lives in Terraform's state file. You need to extract it to a local file so the ssh command can use it.
- Save the private key to a file by running:
terraform output -raw ssh_private_key > docker-key.pem
- Confirm the file was created by running:
ls docker-key.pem
You should see docker-key.pem listed in the output.
Windows requires SSH private key files to have restricted permissions. Without this, the ssh command refuses to use the key.
- Remove inherited permissions and restrict access to your user by running these commands:
icacls docker-key.pem /inheritance:r
icacls docker-key.pem /grant:r "${env:USERNAME}:(R)"
What do these icacls commands do?
The first command removes all inherited permissions from the file, leaving it with no access rules. The second command grants read-only access to your Windows user account.
This ensures only you can read the private key. SSH enforces this restriction to prevent other users on the same machine from stealing your credentials.
Now you are ready to connect. The instance needs 1-2 minutes after creation for its user_data script to finish installing Docker. If you just ran terraform apply, wait a moment before connecting.
- Get your instance's public IP by running:
terraform output instance_public_ip
- SSH into your EC2 instance by running:
ssh -i docker-key.pem ec2-user@[[EC2_IP="your instance public IP address"]]
What is this SSH command doing?
The -i docker-key.pem flag tells SSH which private key file to use for authentication. The ec2-user part is the default username on Amazon Linux instances.
- When prompted with "Are you sure you want to continue connecting?", type yes and press Enter.
You should now see a prompt that starts with [ec2-user@ confirming you are connected to the remote server.
Connection timed out or refused?
If SSH times out, the instance may still be starting. Wait 60 seconds and try again. If it still fails, confirm the IP address from terraform output instance_public_ip matches what you used in the SSH command.
If you see a permissions error about the key file, re-run the icacls commands above.
Help me troubleshoot my SSH connection.
Do you think Docker is ready on this server? What command could confirm it?
- Verify Docker is installed and running on the instance by running:
docker --version
You should see a Docker version number printed, confirming the user_data script completed successfully.
Docker command not found?
The user_data script takes 1-2 minutes to finish. If Docker is not found, wait a minute and try again. You can check the script's progress by running:
sudo systemctl status docker
If it shows active (running), Docker is ready. If the service is not found, the install script is still running.
Help me check if Docker finished installing on my EC2 instance.
Your cloud server is live, Docker is running, and you have SSH access. Next up, you will build and run your first Docker container on this instance.
Build and Run Your Docker Container
In the previous step, you deployed an EC2 instance and confirmed Docker is installed and running. The server is live, but nothing is actually serving traffic yet.
Your EC2 instance needs an application to run. In this step, you will write a simple web server, package it into a Docker container image, and launch that container. This is the core Docker workflow: write code, write a Dockerfile, build, run.
In this step, get ready to:
- Create a Node.js web server that responds with HTML on port 80.
- Package the server into a Docker image using a Dockerfile.
- Run the container and confirm it serves traffic locally.
Create your web application
Every Docker container runs an application. Before you can build an image, you need the application code itself. You will create a lightweight Node.js HTTP server that responds with an HTML page.
- In your SSH session on the EC2 instance, create a project directory and move into it by running these commands:
mkdir app
cd app
- Confirm the directory was created by running:
pwd
You should see /home/ec2-user/app in the output.
- Create the server.js file by running this command:
cat << 'EOF' > server.js
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<h1>Hello from Docker on AWS!</h1><p>Deployed with Terraform</p>");
});
server.listen(80, () => {
console.log("Server running on port 80");
});
EOF
- Confirm the file was created by running:
ls
You should see server.js listed in the output.
What does this code do?
- require("http") loads Node.js's built-in HTTP module. No external packages needed.
- createServer defines a function that runs every time a request hits the server. It sends back an HTML page with a 200 status code.
- server.listen(80) starts the server on port 80, the standard HTTP port.
Don't see server.js?
Make sure you copied the entire command including the EOF on its own line at the end. The heredoc syntax requires the closing EOF to appear alone on a new line with no spaces before it.
You can verify the file contents by running cat server.js.
Help me troubleshoot my server.js file
✔️ Awesome, I've got everything!
Great. Make sure you are still inside the app directory before continuing.
ⓧ I'd like to double check the full code
Your server.js should look exactly like this:
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<h1>Hello from Docker on AWS!</h1><p>Deployed with Terraform</p>");
});
server.listen(80, () => {
console.log("Server running on port 80");
});
Write a Dockerfile and build the image
A Dockerfile is a text file that tells Docker how to package your application into an image. Each line is an instruction that creates a layer in the final image.
- Create the Dockerfile by running this command:
cat << 'EOF' > Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 80
CMD ["node", "server.js"]
EOF
What does each line do?
- FROM node:20-alpine starts from a lightweight Node.js base image (about 130 MB).
- WORKDIR /app sets the working directory inside the container.
- COPY server.js . copies your application file into the container.
- EXPOSE 80 documents that the container listens on port 80.
- CMD ["node", "server.js"] tells Docker what command to run when the container starts.
- Build the Docker image by running this command:
docker build -t my-web-app .
Docker downloads the node:20-alpine base image (this takes a minute on the first build), then executes each instruction from your Dockerfile. You will see progress lines for each step, ending with the image tagged as my-web-app.
What does the -t flag do?
The -t my-web-app flag tags the image with a human-readable name. Without it, you would have to reference the image by its SHA256 hash.
The . at the end tells Docker to use the current directory as the build context, which is where it looks for the Dockerfile and your application files.
Build failed?
If you see an error about the Dockerfile not being found, make sure you are in the app directory. Run pwd to confirm you are in /home/ec2-user/app.
If you see a permission error, your user may not be in the Docker group yet. Run sudo docker build -t my-web-app . instead, or disconnect and reconnect your SSH session so the group membership takes effect.
Help me fix my Docker build
✔️ Awesome, I've got everything!
Great. Your image is built and ready to run as a container.
ⓧ I'd like to double check the full code
Your Dockerfile should look exactly like this:
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 80
CMD ["node", "server.js"]
Run the container
The image is a blueprint. To actually serve traffic, you need to run it as a container. Port mapping connects a port on the EC2 host to a port inside the container so traffic can flow through.
- Start the container by running this command:
docker run -d -p 80:80 --name web my-web-app
What do these flags do?
- -d runs the container in detached mode (in the background), so you get your terminal back.
- -p 80:80 maps port 80 on the host (EC2 instance) to port 80 inside the container.
- --name web gives the container a friendly name so you can reference it easily.
- my-web-app is the image you just built.
- Verify the container is running by running:
docker ps
You should see a table with one row showing your web container, the image my-web-app, status Up, and ports 0.0.0.0:80->80/tcp.
Before you test the server, what do you think curl localhost:80 will return?
- Test the server locally by running:
curl localhost:80
You should see the HTML response: <h1>Hello from Docker on AWS!</h1><p>Deployed with Terraform</p>.
Container not running or curl fails?
If docker ps shows nothing, the container may have exited immediately. Run docker logs web to see any error messages from the application.
If curl returns Connection refused, check that the port mapping is correct. Run docker ps -a to see all containers including stopped ones.
If you see a permission error when running Docker commands, reconnect your SSH session so the docker group membership takes effect.
Help me debug my Docker container
Your web application is running inside a Docker container on your cloud server. Next up, you will try to access it from your browser and discover why the security group matters.
Open Your App to the Internet
Your Docker container is running and responding on port 80 inside the EC2 instance. But can the outside world reach it?
Right now, the security group only allows SSH traffic on port 22. HTTP traffic on port 80 is blocked. In this step, you will discover that firsthand, then fix it by updating your Terraform configuration and re-applying. This is the power of Infrastructure as Code: infrastructure changes are code changes.
In this step, get ready to:
- Attempt to access your app from a browser and see it time out.
- Update the security group in Terraform to allow HTTP traffic.
- Re-apply the config and confirm your app is publicly accessible.
Try to access your app from a browser
Your container proved it works when you ran curl localhost:80 from inside the instance. Now try reaching it from outside.
- Open your web browser on your local machine.
- In the address bar, type http:// followed by the public IP address Terraform printed earlier (you can also get it by running terraform output instance_public_ip in your project folder).
Before you press Enter, ask yourself: do you think the page will load successfully, or will something go wrong?
- Press Enter and wait about 10-15 seconds.
The page times out. Your browser cannot connect to the server.
Why does the page time out?
Your app IS running inside the instance. The problem is the security group.
A security group acts like a firewall for your EC2 instance. Right now, docker-project-sg only allows inbound traffic on port 22 (SSH). Port 80 (HTTP) is blocked, so your browser's request never reaches the container.
This is the designed obstacle. You built the app, deployed it, confirmed it works internally, but the network blocks the outside world from seeing it. The fix is a one-line infrastructure change in Terraform.
Update the security group in Terraform
Instead of clicking through the AWS Console to modify firewall rules, you will edit the Terraform config file. This keeps your infrastructure reproducible and version-controlled.
- Switch back to VS Code with your docker-on-aws project folder open.
- Open main.tf.
- Find the aws_security_group resource block. Locate the closing brace of the SSH ingress block:
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
- Directly below that closing brace, add a new ingress block to allow HTTP traffic on port 80:
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
What does this code do?
- description = "HTTP" labels this rule so you know its purpose at a glance.
- from_port = 80 and to_port = 80 open exactly one port for web traffic.
- cidr_blocks = ["0.0.0.0/0"] allows traffic from any IP address on the internet.
- Save main.tf.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf before moving on.
ⓧ I'd like to double check the full code
Your main.tf is long, so confirm these sections are present in order by scrolling through the file:
- The terraform block with required providers (aws and tls).
- The provider "aws" block with region us-east-1.
- The data "aws_ami" block filtering for Amazon Linux 2023.
- The tls_private_key and aws_key_pair resources.
- The aws_security_group resource with TWO ingress blocks (SSH on port 22, HTTP on port 80) and one egress block.
- The aws_instance resource with user_data that installs Docker.
Not sure where to add the block?
The new HTTP ingress block goes between the SSH ingress block's closing brace } and the egress block's opening. Make sure it is still inside the aws_security_group resource (before the final } of the resource).
help me place the HTTP ingress block in the correct location in main.tf
Re-apply Terraform and verify your app is live
Terraform compares your updated config file against the real infrastructure in AWS. It will detect only the security group changed and update it in-place without touching your EC2 instance or container.
- Return to your PowerShell terminal (make sure you are on your local machine in the docker-on-aws folder, not inside an SSH session).
- Apply the updated configuration by running:
terraform apply
- Terraform shows a plan with 1 resource to change. Type yes and press Enter.
Terraform updates the security group. You should see a message ending with Apply complete! Resources: 0 added, 1 changed, 0 destroyed..
Why only 1 changed?
Terraform tracks every resource it manages. Because you only modified the security group block in main.tf, Terraform leaves the EC2 instance, key pair, and everything else untouched. This surgical precision is one of the key benefits of IaC.
Now the moment of truth. Before you refresh the browser, what do you expect to see this time?
- Switch back to your browser and refresh the page at http://<public-ip>.
You should see a page with the heading "Hello from Docker on AWS!" and the text "Deployed with Terraform".
Your app is live and accessible to anyone on the internet.
Page still timing out?
- Confirm terraform apply completed successfully with "1 changed" in the output.
- Make sure you are using http:// (not https://) in the URL. Your server does not have TLS configured.
- Verify your container is still running by SSH-ing back in and running docker ps. If it crashed, restart it with docker start web.
help me debug why my browser cannot reach the app on port 80 after updating the security group
Secret mission
Add Environment Variables and a Health Check
Your container works, but it's fragile. The greeting is hardcoded, and Docker has no way to know if your app is actually responding to requests. In production, containers need to be configurable and self-monitoring. This mission adds both.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your EC2 instance is covered by AWS Free Tier for accounts created after July 15, 2025, but those credits last for 6 months or until used up.
Cost warning
If your AWS Free Tier credits expire or are exhausted, the t3.small instance will begin incurring charges. Running it 24/7 outside of free tier costs approximately $0.02 per hour. If you are done experimenting, choose the Delete option below to guarantee $0 charges going forward.
Resources you used:
- 1 EC2 instance (docker-project-host, t3.small) in us-east-1.
- 1 Security Group (docker-project-sg).
- 1 Key Pair (docker-project-key).
- Local project folder (docker-on-aws) containing Terraform state, configuration files, and your SSH key.
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment further with Docker on your instance.
- Your instance remains running and accessible via its public IP.
- Free Tier credits cover the t3.small instance for 6 months (accounts created after July 15, 2025).
- Monitor your usage in the AWS Billing dashboard to avoid unexpected charges.
- Your local docker-on-aws folder stays on your Desktop with all Terraform files and the SSH key.
Pause - I'll come back to this later
Stop the instance to preserve your free tier credits. You can restart it later and reconnect via SSH.
- Open the EC2 console at https://console.aws.amazon.com/ec2/.
- In the left navigation pane, choose Instances.
- Select your docker-project-host instance.
- Choose Instance state, then Stop instance.
- Choose Stop when prompted for confirmation.
What happens when you stop the instance?
You will not be charged for compute while the instance is stopped. A small EBS storage charge may apply (pennies per month). Your Docker container and files on the instance are preserved on disk.
The instance will receive a new public IP address when you start it again. Run terraform output instance_public_ip after restarting to get the new address. You will also need to restart Docker and your container manually.
Delete - I don't want to use this again
Remove all AWS resources and local project files. This guarantees $0 charges going forward.
Destroy all AWS resources by running this command from your docker-on-aws folder in PowerShell:
terraform destroy
- When prompted, type yes and press Enter.
- Terraform removes the EC2 instance, security group, and key pair from AWS.
Delete your local project folder by running:
cd ~/Desktop
Remove-Item -Recurse -Force docker-on-aws
- This removes all Terraform files, state, and your local SSH key (docker-key.pem).
Optionally, remove your stored AWS credentials by deleting the .aws folder:
Remove-Item -Recurse -Force $HOME\.aws
When would I keep my AWS credentials?
If you plan to do more AWS projects in the future, keep the .aws folder. It stores your CLI configuration and does not incur any cost. Only remove it if you want a completely clean slate.
That's a Wrap!
That's a Wrap!
Nice work! You just went from an empty folder to a live web application running inside a Docker container on AWS, provisioned entirely through code with Terraform.
You've learned how to:
- Written a Terraform configuration that provisions an EC2 instance, SSH key pair, and security group with a single terraform apply command.
- Built a custom Docker image from a Dockerfile, ran it as a container on a cloud server, and accessed it live from your browser.
- Diagnosed a blocked-port networking issue and fixed it by updating your Terraform config, experiencing the full Infrastructure as Code feedback loop.
- Secret Mission: Added environment variables and a Docker HEALTHCHECK to make your container production-ready.
Ready to quiz yourself?