Deploy a Web App with CodeDeploy
Deploy a Java web app to EC2 with lifecycle hooks and health check validation.
Introduction
30 Second Summary
Imagine pushing a code update to your live app, walking away, and finding out hours later that it broke everything. Your users noticed before you did. Automated health checks exist to prevent exactly this scenario.
In this project, you will deploy a Java web app to a production server using AWS CodeDeploy and build a ValidateService health check that automatically verifies every deployment succeeded. You will also intentionally break a deployment to prove the health check catches real failures.
What You'll Build
You will deploy your web app, watch the health check confirm it is alive, then break it on purpose and watch CodeDeploy catch the failure at the ValidateService stage before anyone else notices.
By the end of this project, you'll have:
- Your web app running live on a production EC2 instance launched with CloudFormation, accessible via a public URL you can share.
- A complete set of deployment lifecycle scripts that CodeDeploy executes in sequence to install dependencies, start your server, stop your server, and validate health.
- A ValidateService health check that curls your running app and automatically fails the deployment if it gets a bad response.
- Secret Mission: Trigger a deployment failure by deploying a broken artifact, watch the health check catch it, then recover by redeploying the working version.
Are there any prerequisites?
This project is Day 5 of the 6 Day DevOps Challenge. You need a working EC2 dev instance, a GitHub repo with your Java web app, a CodeArtifact repository, and a CodeBuild project with an S3 artifact bucket from Days 1-4.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This is where you commit to the goal of this project: automating deployment with built-in health checks that catch broken releases before they reach your users.
Launch Production Infrastructure with CloudFormation
Your dev EC2 instance is where you write and test code. But users need a separate production instance to access your deployed app.
Clicking through 10+ console screens to build a VPC, subnet, internet gateway, route table, security group, and EC2 instance by hand is slow and error-prone. CloudFormation lets you define all of those resources in a single template file and spin them up in one action.
In this step, get ready to:
- Upload a CloudFormation template and create a production infrastructure stack.
- Monitor the stack creation until all resources reach CREATE_COMPLETE.
- Confirm your production EC2 instance is running and record its public address.
Create the CloudFormation stack
The nextworkwebapp.yaml template provided on this project page defines everything your production environment needs. You will upload it to CloudFormation and let AWS build the entire networking stack plus the instance for you.
- Download the nextworkwebapp.yaml file from this project page to your local computer.
- Find your public IP address by searching "what is my IP" in your browser. Copy the IP address shown (for example, 203.0.113.42). You will need this in a moment.
- Open the AWS Management Console and navigate to the CloudFormation service using the search bar at the top.
- On the Stacks page, click Create stack at the top right.
- Select With new resources (standard).
- Under Prerequisite - Prepare template, choose Choose an existing template.
- Under Specify template, select Upload a template file.
- Click Choose file and select the nextworkwebapp.yaml file you downloaded.
- Click Next.
- In the Stack name field, enter NextWorkCodeDeployEC2Stack.
- In the Parameters section, find the MyIP parameter.
- Enter your public IP address followed by /32 (for example, 203.0.113.42/32).
- Click Next.
Why /32 after your IP address?
The /32 suffix means "this single IP address only." It restricts SSH access to the production instance so that only your computer can connect. Without it, the security group rule would be incomplete.
- On the Configure stack options page, leave the defaults (rollback on failure is already enabled). Click Next.
- On the Review and create page, scroll to the bottom.
- Check the box next to I acknowledge that AWS CloudFormation might create IAM resources.
- Click Submit.
What is CloudFormation?
CloudFormation is AWS's Infrastructure as Code service. You describe your resources in a template file, and CloudFormation creates, configures, and connects them for you. If something fails, it automatically rolls back to a clean state.
The template you uploaded defines a VPC, subnet, internet gateway, route table, security group, and an EC2 instance with the CodeDeploy agent pre-installed.
Monitor the stack creation
CloudFormation is now provisioning six resources. You can watch them appear one by one.
- After clicking Submit, you are taken to the stack details page.
- Click the Events tab to watch resources being created in real time.
- Click the refresh icon periodically to see new events appear.
- Wait until the stack status at the top changes to CREATE_COMPLETE. This typically takes 2-3 minutes.
- Click the Resources tab to see all the resources CloudFormation created for you.
What resources did CloudFormation create?
The Resources tab shows the full list. Your template created a VPC (a private network), a subnet (a section of that network), an internet gateway (the exit to the public internet), a route table (traffic directions), a security group (firewall rules), and a production EC2 instance.
The EC2 instance comes pre-tagged with role: webserver. CodeDeploy will use this tag later to find your production instance automatically.
Stack stuck in CREATE_IN_PROGRESS for more than 5 minutes?
Check the Events tab for any red CREATE_FAILED entries. A common cause is an invalid MyIP value (make sure you included /32 at the end). If the stack fails, delete it from the Stacks page and recreate it with the corrected parameter.
help me debug my CloudFormation stack creation failure
Confirm the production EC2 instance is running
The stack reports success, but you should confirm the instance is actually running and grab its public address for later.
- Navigate to the EC2 service using the search bar at the top of the console.
- In the left sidebar, click Instances.
Before you look at the list, predict: how many instances do you expect to see running? Remember, you already have your dev instance from earlier days.
- You should see two instances. One is your dev instance from earlier. The new one was created by CloudFormation.
- Select the new instance (the one you did not create manually).
- In the Tags tab, confirm it has a tag with key role and value webserver.
- Confirm the Instance state column shows Running.
- In the Details tab, find the Public IPv4 DNS field. Copy this value and save it somewhere (a text file or sticky note). You will need it in a later step to access your deployed app.
Distinguish your instances by tags
Your dev instance does not have the role: webserver tag. If you are unsure which instance is which, click each one and check its Tags tab. The production instance also shows the CloudFormation stack name in its tags.
Your production infrastructure is live. Next up, you will create the deployment scripts that tell CodeDeploy exactly how to install, start, stop, and validate your web app on this instance.
Create Deployment Scripts and appspec.yml
Your production infrastructure is ready. Now you need to tell CodeDeploy exactly how to deploy your application to that instance.
CodeDeploy does not know how to install your dependencies, start your server, or verify the app is healthy. You need to provide shell scripts for each of those jobs, plus a configuration file called appspec.yml that maps each script to a specific deployment lifecycle event.
In this step, get ready to:
- Create lifecycle scripts that install dependencies, start your server, and stop your server.
- Write a health check script that validates deployments automatically.
- Configure an appspec.yml that tells CodeDeploy when to run each script.
Create the scripts folder and install dependencies script
Your deployment scripts live in a dedicated folder at the project root. The first script handles installing the web server software your app needs to run.
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, top-left of keyboard).
- Create the scripts folder at the root of your project by running this command:
mkdir scripts
- In the VS Code Explorer sidebar, expand the new scripts folder.
- Right-click on the scripts folder and select New File.
- Name the file install_dependencies.sh and press Enter.
- Paste the following code into the file:
#!/bin/bash
sudo yum install tomcat -y
sudo yum -y install httpd
sudo cat <<EOF > /etc/httpd/conf.d/tomcat_manager.conf
<VirtualHost *:80>
ServerAdmin root@localhost
ServerName app.nextwork.com
DefaultType text/html
ProxyRequests off
ProxyPreserveHost On
ProxyPass / http://localhost:8080/nextwork-web-project/
ProxyPassReverse / http://localhost:8080/nextwork-web-project/
</VirtualHost>
EOF
What does this script do?
This script runs during the BeforeInstall lifecycle event. It installs two services your app needs.
- Tomcat is the Java application server that runs your WAR file.
- httpd (Apache) handles incoming traffic on port 80 and acts as a reverse proxy, forwarding requests to Tomcat on port 8080.
- The VirtualHost block configures that proxy relationship so users hit port 80 and Apache transparently routes to your Java app.
- Save install_dependencies.sh with Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the file exists by running:
ls scripts/
You should see install_dependencies.sh listed in the output.
Don't see the file?
- Make sure you created the file inside the scripts/ folder, not at the project root.
- Check that you saved the file after pasting the content.
My install_dependencies.sh file isn't showing up in the scripts folder.
Create the start and stop server scripts
CodeDeploy needs scripts to start your server after deploying new code and to stop it before replacing files. These run during the ApplicationStart and ApplicationStop lifecycle events.
- Right-click on the scripts folder and select New File.
- Name the file start_server.sh and paste this code:
#!/bin/bash
sudo systemctl start tomcat.service
sudo systemctl enable tomcat.service
sudo systemctl start httpd.service
sudo systemctl enable httpd.service
What does this script do?
It starts both Tomcat and Apache, then enables them so they automatically restart if the instance reboots. Starting Tomcat makes your WAR file accessible on port 8080. Starting httpd exposes port 80 to the outside world through the reverse proxy you configured.
- Save start_server.sh.
- Right-click on the scripts folder again, select New File, and name it stop_server.sh.
- Paste this code:
#!/bin/bash
isExistApp="$(pgrep httpd)"
if [[ -n $isExistApp ]]; then
sudo systemctl stop httpd.service
fi
isExistApp="$(pgrep tomcat)"
if [[ -n $isExistApp ]]; then
sudo systemctl stop tomcat.service
fi
Why check before stopping?
The pgrep command checks whether each service is actually running before trying to stop it. On the very first deployment to a fresh instance, neither Tomcat nor httpd exists yet. Without this check, systemctl stop would return a non-zero exit code and CodeDeploy would mark the deployment as failed.
- Save stop_server.sh.
- Confirm all three scripts exist by running:
ls scripts/
You should see install_dependencies.sh, start_server.sh, and stop_server.sh listed.
Missing a script?
- Check the VS Code Explorer sidebar to confirm all files are inside the scripts/ folder, not at the project root.
- Verify you saved each file after pasting its content.
One of my scripts isn't appearing in the ls output.
Create the health check script
The most important script is the health check. This runs during the ValidateService lifecycle event, which is the last event in the deployment sequence. If this script fails, CodeDeploy marks the entire deployment as failed.
- Right-click on the scripts folder, select New File, and name it validate_service.sh.
- Paste this code:
#!/bin/bash
sleep 10
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:80)
if [ "$http_code" -eq 200 ]; then
echo "Health check passed: HTTP $http_code"
exit 0
else
echo "Health check failed: HTTP $http_code"
exit 1
fi
What does this health check do?
This is the script that proves your deployment actually worked. It follows a simple pattern.
- sleep 10 waits 10 seconds for Tomcat and Apache to fully start before testing.
- curl sends a request to http://localhost:80 and captures only the HTTP status code.
- If the status is 200, the script exits with code 0 (success). If not, it exits with code 1. A non-zero exit code tells CodeDeploy the deployment failed.
- Save validate_service.sh.
- Confirm all four scripts exist by running:
ls scripts/
You should see all four files: install_dependencies.sh, start_server.sh, stop_server.sh, and validate_service.sh.
Health check script not appearing?
- Confirm the filename is exactly validate_service.sh with an underscore, not a hyphen.
- Make sure the file is saved inside the scripts/ folder.
My validate_service.sh file is missing or has the wrong name.
Why ValidateService matters
Without a health check, CodeDeploy finishes the deployment the moment your start script runs. It has no idea whether the app actually came up healthy. The ValidateService hook is the last event in the deployment lifecycle. It gives you a gate that catches broken deployments automatically.
Create the appspec.yml deployment configuration
The appspec.yml file is the blueprint CodeDeploy reads to understand your deployment. It defines which files to copy and which scripts to run at each lifecycle stage.
- In the VS Code Explorer sidebar, right-click on the project root (the top-level nextwork-web-project folder).
- Select New File and name it appspec.yml.
- Paste the following configuration:
version: 0.0
os: linux
files:
- source: /target/nextwork-web-project.war
destination: /usr/share/tomcat/webapps/
hooks:
BeforeInstall:
- location: scripts/install_dependencies.sh
timeout: 300
runas: root
ApplicationStart:
- location: scripts/start_server.sh
timeout: 300
runas: root
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 300
runas: root
ValidateService:
- location: scripts/validate_service.sh
timeout: 300
runas: root
What does each section do?
- version: 0.0 is the AppSpec format version that CodeDeploy expects.
- files tells CodeDeploy to copy your WAR file from the artifact into Tomcat's webapps/ directory.
- hooks maps each lifecycle event to a script. The events run in this order: ApplicationStop, BeforeInstall, Install (automatic file copy), ApplicationStart, then ValidateService.
- timeout: 300 gives each script up to 300 seconds (5 minutes) to complete before CodeDeploy considers it failed.
- runas: root ensures the scripts run with the permissions needed to install packages and manage services.
Why does ApplicationStop come first?
On every deployment after the first one, CodeDeploy stops the currently running app before replacing its files. On the very first deployment to a fresh instance, the ApplicationStop hook does not run because no previous deployment exists. That is why your stop_server.sh checks whether the services are running before trying to stop them.
- Save appspec.yml.
Before you verify, what do you expect git status to show right now? You have not committed anything yet, so think about how many new files Git should detect.
- Verify all five files are detected by running:
git status
You should see five untracked files listed in red: appspec.yml and the four scripts under scripts/.
Not seeing all five files?
- If appspec.yml is missing, check that you created it at the project root, not inside the scripts/ folder. The AppSpec file must be in the root of the directory structure.
- If a script is missing, check the VS Code Explorer sidebar to verify the file is inside the scripts/ folder with the correct filename.
- Make sure you saved all files before running git status.
Git status is not showing all my deployment files.
✔️ Awesome, I've got everything!
Great. Double check you have saved all five files before moving on.
ⓧ I'd like to double check the full code
Here is each file for reference. Scroll up and confirm your file contents match.
scripts/install_dependencies.sh
#!/bin/bash
sudo yum install tomcat -y
sudo yum -y install httpd
sudo cat <<EOF > /etc/httpd/conf.d/tomcat_manager.conf
<VirtualHost *:80>
ServerAdmin root@localhost
ServerName app.nextwork.com
DefaultType text/html
ProxyRequests off
ProxyPreserveHost On
ProxyPass / http://localhost:8080/nextwork-web-project/
ProxyPassReverse / http://localhost:8080/nextwork-web-project/
</VirtualHost>
EOF
scripts/start_server.sh
#!/bin/bash
sudo systemctl start tomcat.service
sudo systemctl enable tomcat.service
sudo systemctl start httpd.service
sudo systemctl enable httpd.service
scripts/stop_server.sh
#!/bin/bash
isExistApp="$(pgrep httpd)"
if [[ -n $isExistApp ]]; then
sudo systemctl stop httpd.service
fi
isExistApp="$(pgrep tomcat)"
if [[ -n $isExistApp ]]; then
sudo systemctl stop tomcat.service
fi
scripts/validate_service.sh
#!/bin/bash
sleep 10
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:80)
if [ "$http_code" -eq 200 ]; then
echo "Health check passed: HTTP $http_code"
exit 0
else
echo "Health check failed: HTTP $http_code"
exit 1
fi
appspec.yml
version: 0.0
os: linux
files:
- source: /target/nextwork-web-project.war
destination: /usr/share/tomcat/webapps/
hooks:
BeforeInstall:
- location: scripts/install_dependencies.sh
timeout: 300
runas: root
ApplicationStart:
- location: scripts/start_server.sh
timeout: 300
runas: root
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 300
runas: root
ValidateService:
- location: scripts/validate_service.sh
timeout: 300
runas: root
Your deployment scripts and configuration are ready. Next up, you will create the CodeDeploy application and deployment group that tells AWS where to deploy your app.
Set Up CodeDeploy Application and Deployment Group
Your deployment scripts are written and ready. They tell CodeDeploy exactly how to install, start, stop, and validate your app.
But CodeDeploy still does not know your application exists, or which EC2 instance to target. In this step, you will create a CodeDeploy application (the logical container), an IAM role that grants CodeDeploy permission to act on your behalf, and a deployment group that points at your production instance.
In this step, get ready to:
- Create a CodeDeploy application and an IAM service role.
- Create a deployment group that targets your production EC2 instance by tag.
Create the CodeDeploy application and IAM role
A CodeDeploy application is a named container that holds your deployment groups and revisions. You also need an IAM role that lets the CodeDeploy service interact with EC2 on your behalf.
- In the AWS Management Console, search for CodeDeploy and open the service.
- In the left sidebar, click Applications.
- Click Create application.
- Enter nextwork-devops-cicd in the Application name field.
- Under Compute platform, select EC2/On-premises.
- Click Create application.
You should see a success banner confirming your application nextwork-devops-cicd was created.
What is a CodeDeploy application?
Think of it as a named folder. It does not contain any code itself. It groups together your deployment groups (the targets) and revisions (the artifacts you deploy).
Now you need an IAM role that grants CodeDeploy permission to coordinate deployments on EC2 instances. You will create this in the IAM console.
- Open a new browser tab and navigate to the IAM console.
- In the left sidebar, click Roles.
- Click Create role.
- Under Trusted entity type, select AWS service.
- In the Use case dropdown, search for and select CodeDeploy.
- Select the CodeDeploy option (not CodeDeploy - ECS or CodeDeploy - Lambda).
- Click Next.
On the permissions page, the AWSCodeDeployRole managed policy is already attached. This policy gives CodeDeploy the permissions it needs to work with EC2, Auto Scaling, Elastic Load Balancing, CloudWatch, and S3.
- Click Next.
- Enter NextWorkCodeDeployRole in the Role name field.
- Click Create role.
You should see a success banner confirming that role NextWorkCodeDeployRole was created.
Don't see the AWSCodeDeployRole policy?
Make sure you selected the CodeDeploy use case (not ECS or Lambda) on the previous screen. Go back and reselect it. The correct policy attaches automatically when the right use case is chosen.
help me set up my CodeDeploy IAM role
Create the deployment group
A deployment group tells CodeDeploy which EC2 instances to target. It finds them using the tags you set on your production instance.
- Switch back to your CodeDeploy browser tab.
- Click your application name nextwork-devops-cicd to open it.
- Click Create deployment group.
- Enter nextwork-devops-cicd-deploymentgroup in the Deployment group name field.
- In the Service role dropdown, select NextWorkCodeDeployRole.
- Under Deployment type, select In-place.
What is an in-place deployment?
In-place means CodeDeploy stops your current app on the existing instance, deploys the new version onto the same instance, then starts it back up. The alternative (blue/green) launches entirely new instances and switches traffic over.
In-place is simpler and works well for a single-instance setup like yours.
Now you need to tell CodeDeploy how to find your production instance. It uses EC2 tags to match instances to a deployment group.
- Under Environment configuration, select Amazon EC2 instances.
- In the Key field, enter role.
- In the Value field, enter webserver.
You should see 1 unique matched instance appear below the tag fields. This confirms CodeDeploy found your production EC2 instance by its tag.
Seeing 0 matched instances?
Double check that the tag key is exactly role (lowercase) and the value is exactly webserver (lowercase). Tags are case-sensitive. Also confirm your production EC2 instance from the CloudFormation stack is still in the Running state.
help me troubleshoot my deployment group instance matching
- Under Agent configuration with AWS Systems Manager, select Now and schedule updates.
- Set the schedule to 14 days with the Basic scheduler.
- Under Deployment settings, keep the default CodeDeployDefault.AllAtOnce.
- Under Load balancer, uncheck Enable load balancing.
- Click Create deployment group.
Why disable load balancing?
Load balancers distribute traffic across multiple instances. You only have one production instance, so a load balancer would add cost and complexity without any benefit.
Verify your CodeDeploy setup
Before you move on, what do you expect to see when you open your CodeDeploy application? How many deployment groups should be listed, and how many instances should it show?
- In the CodeDeploy console sidebar, click Applications.
- Click nextwork-devops-cicd to open it.
You should see nextwork-devops-cicd-deploymentgroup listed under Deployment groups with 1 matched instance. This confirms CodeDeploy knows your app and where to deploy it.
Your deployment pipeline is fully wired up. CodeDeploy knows your app, has permission to act, and can find your production instance. Next up, you will push your scripts to GitHub, rebuild the artifact, and deploy your web app live.
Deploy and Validate
Your CodeDeploy application knows which EC2 instance to target, and your deployment scripts are ready to install dependencies, start services, and validate health.
Now it's time to connect all the pieces. In this step, you'll push your deployment files to GitHub, rebuild in CodeBuild, and deploy to production. You'll watch CodeDeploy execute every lifecycle hook in sequence, ending with the ValidateService health check that proves your app is actually responding.
In this step, get ready to:
- Update buildspec.yml to include deployment files in the build artifact.
- Rebuild the project in CodeBuild to produce a deployment-ready artifact.
- Deploy to production and confirm the health check passes.
Update buildspec.yml and push to GitHub
Right now, CodeBuild only packages the WAR file as its output artifact. CodeDeploy needs appspec.yml and the scripts/ folder included in that artifact too. Without them, CodeDeploy won't know how to deploy your app.
- In VS Code, open buildspec.yml from the root of your nextwork-web-project folder.
- Scroll down to the artifacts section at the bottom. Find these lines:
artifacts:
files:
- target/nextwork-web-project.war
discard-paths: no
- Add appspec.yml and scripts/**/* as new items under files:, so the section now looks like this:
artifacts:
files:
- target/nextwork-web-project.war
- appspec.yml
- scripts/**/*
discard-paths: no
What do these new lines do?
The appspec.yml line tells CodeBuild to include your deployment specification in the output artifact. CodeDeploy reads this file to know which hooks to run.
The scripts/**/* pattern uses a glob to capture every file inside the scripts/ folder. The discard-paths: no setting preserves the folder structure so CodeDeploy can find each script at the path specified in appspec.yml.
- Save buildspec.yml.
✔️ Awesome, I've got everything!
Great. Double check you've saved buildspec.yml before moving on.
ⓧ I'd like to double check the full code
Here is the full buildspec.yml for comparison. Your account ID and region values will differ from the placeholders shown below.
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
pre_build:
commands:
- echo Logging in to AWS CodeArtifact...
- CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain nextwork --domain-owner <your_aws_account_id> --region <your-region> --query authorizationToken --output text`
- export CODEARTIFACT_AUTH_TOKEN
build:
commands:
- echo Build started on `date`
- mvn clean install -s settings.xml
post_build:
commands:
- echo Build completed on `date`
- echo Packaging artifacts...
- mvn package -s settings.xml
artifacts:
files:
- target/nextwork-web-project.war
- appspec.yml
- scripts/**/*
discard-paths: no
Now commit all your new files and push them to GitHub.
- Add all changes to git by running this command:
git add appspec.yml scripts/ buildspec.yml
- Commit your changes by running:
git commit -m "Add CodeDeploy deployment scripts and appspec"
- Push to GitHub by running:
git push
You should see output confirming your branch was pushed to origin.
Push rejected or authentication error?
Make sure your dev EC2 instance still has GitHub credentials configured. If you see a permission error, re-authenticate with GitHub using the same method you used on Day 2 of the challenge.
Help me re-authenticate with GitHub from my EC2 instance
Start a new build in CodeBuild
Your updated code is now on GitHub. CodeBuild needs to run a fresh build so the output artifact includes appspec.yml and your scripts alongside the WAR file.
- Switch to the AWS Management Console in your browser.
- Navigate to the CodeBuild service.
- Click on your build project nextwork-devops-cicd.
- Click Start build.
- Leave all defaults and click Start build again to confirm.
Wait for the build to complete. This typically takes 2-3 minutes.
- Confirm the build status shows Succeeded with a green indicator.
Where is my artifact stored?
CodeBuild uploads the finished artifact to your S3 bucket as a zip file. This zip now contains target/nextwork-web-project.war, appspec.yml, and the entire scripts/ folder. CodeDeploy will pull from this S3 location during deployment.
- Navigate to the S3 service.
- Open your artifact bucket (the one your CodeBuild project outputs to).
- Confirm you can see the updated artifact file. Note the S3 URI of this artifact. You'll need it in the next substep.
Copy the S3 URI now
Select the checkbox next to the artifact file and click Copy S3 URI. Keep this value handy for creating the deployment.
Build failed?
Check the build logs for errors. The most common cause is a syntax error in buildspec.yml. Make sure your artifacts section uses correct YAML indentation (two spaces per level).
Help me debug my CodeBuild failure
Create a deployment in CodeDeploy
Your artifact is ready in S3 with all the deployment files included. Now you'll tell CodeDeploy to pull that artifact and deploy it to your production instance.
- Navigate to the CodeDeploy service.
- In the left navigation pane, expand Deploy and select Applications.
- Click on nextwork-devops-cicd.
- Select the Deployments tab.
- Click Create deployment.
Configure the deployment settings on the Create deployment page.
- For Deployment group, select nextwork-devops-cicd-deploymentgroup.
- For Revision type, select My application is stored in Amazon S3.
- In the Revision location field, paste the S3 URI you copied from your artifact bucket.
- For Revision file type, select .zip.
- Leave all other settings at their defaults.
- Click Create deployment.
CodeDeploy begins running your deployment lifecycle hooks in sequence. You'll see a Deployment lifecycle events section on the deployment details page.
- Watch the lifecycle events progress. Each event transitions from Pending to In Progress to Succeeded.
What lifecycle events will you see?
CodeDeploy runs hooks in this order for an in-place deployment: ApplicationStop, DownloadBundle, BeforeInstall, Install, AfterInstall, ApplicationStart, ValidateService.
On a first deployment, ApplicationStop shows as Skipped because no previous version of the app exists to stop.
- Wait for all events to complete. The final event is ValidateService. This is your health check script running.
- Confirm the deployment status shows Succeeded and all lifecycle events have green checkmarks.
What just happened during ValidateService?
Your validate_service.sh script waited 10 seconds for Tomcat and httpd to start, then sent a curl request to http://localhost:80. It received an HTTP 200 response and exited with code 0, which told CodeDeploy the deployment is healthy.
If the app had returned anything other than 200, the script would have exited with code 1 and CodeDeploy would have marked the deployment as failed.
Before you check the app in your browser, what do you expect to see when you visit your production EC2 instance's public DNS?
- Open a new browser tab.
- Paste your production EC2 instance's Public IPv4 DNS into the address bar. Make sure the URL starts with http:// (not https://).
- Press Enter to visit the page.
You should see your web application running live. This is the same app you built on your dev instance, now served from your production EC2 instance through the Apache reverse proxy.
Page not loading or connection timeout?
Make sure you're using http:// and not https://. The security group only allows HTTP traffic on port 80.
If the page still doesn't load, check that the production EC2 instance's security group allows inbound traffic on port 80 from your IP address. The CloudFormation template configured this using your MyIP parameter.
Help me troubleshoot my production EC2 connection
Your deployment pipeline is complete. You can push code, rebuild, and deploy to production with a health check that automatically catches broken deployments. Next up, you'll put that health check to the test by intentionally breaking things.
Secret mission
Trigger and Recover from a Failed Health Check
Your ValidateService health check passed on a working deployment. But how do you know it would actually catch a broken one? In this mission, you intentionally break your app to prove the health check works, then recover by redeploying the working version.
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. The production EC2 instance (t2.micro) is free tier eligible, CodeDeploy has no additional charge for EC2 deployments, and the VPC networking resources are free.
Cost warning
If your AWS free tier has expired (12 months after account creation), the running t2.micro production EC2 instance will incur hourly charges. Stop or terminate the instance if you are no longer within the free tier.
Resources you used:
- CloudFormation stack (NextWorkCodeDeployEC2Stack) containing: VPC, subnet, internet gateway, route table, security group, production EC2 instance.
- CodeDeploy application (nextwork-devops-cicd) with deployment group and deployment history.
- IAM role (NextWorkCodeDeployRole) with the AWSCodeDeployRole managed policy.
- Updated S3 artifact in your existing CodeBuild S3 bucket.
Keep everything running
No action needed. Choose this if you are still actively building or plan to continue with Day 6 of the DevOps Challenge.
- Your web app stays accessible via the production EC2 instance's public DNS.
- The EC2 instance continues to run. Free tier covers 750 hours per month of t2.micro usage.
- Your dev instance, CodeBuild project, and S3 bucket remain ready for Day 6.
Pause - I'll come back to this later
Stop the production EC2 instance to halt compute charges while preserving your full setup.
- Open the EC2 console and select your production instance (tagged role: webserver).
- Choose Instance state, then Stop instance.
- Confirm by clicking Stop.
- Your CloudFormation stack, CodeDeploy application, and IAM role remain intact. When you return, start the instance and redeploy.
Delete - I don't want to use this again
Remove all production resources created in this project. Keep your dev instance, CodeBuild project, and S3 bucket for Day 6.
- Delete the CloudFormation stack (this removes the VPC, subnet, internet gateway, route table, security group, and production EC2 instance all at once):
- Open the CloudFormation console.
- On the Stacks page, select NextWorkCodeDeployEC2Stack.
- Choose Delete, then confirm Delete.
- Wait for the stack status to reach DELETE_COMPLETE.
- Delete the CodeDeploy application:
- Open the CodeDeploy console.
- In the navigation pane, expand Deploy, then choose Applications.
- Choose nextwork-devops-cicd.
- Choose Delete application.
- Enter delete to confirm, then choose Delete.
- Delete the IAM role:
- Open the IAM console.
- In the navigation pane, choose Roles.
- Select the checkbox next to NextWorkCodeDeployRole.
- Choose Delete at the top of the page.
- Enter the role name to confirm, then choose Delete.
What about the S3 artifact?
The updated artifact lives in your existing CodeBuild S3 bucket. Keep this bucket intact for Day 6 of the DevOps Challenge. The artifact is just a zip file and does not incur meaningful storage costs.
Nice Work!
Nice Work!
You just built a fully automated deployment pipeline with a built-in safety net. Your Java web app now deploys to a production EC2 instance through CodeDeploy, and a health check script automatically catches broken deployments before they go unnoticed.
You've learned how to:
- Launch production infrastructure with CloudFormation, defining an entire networking stack and EC2 instance in a single template instead of clicking through console screens.
- Create deployment lifecycle scripts and an appspec.yml that tell CodeDeploy exactly how to install dependencies, start and stop services, and validate health after every deployment.
- Deploy your web app using AWS CodeDeploy with a ValidateService health check that curls your running application and fails the deployment automatically if it gets a bad HTTP response.
- Triggered a failed deployment by intentionally breaking the app, watched CodeDeploy catch the problem at the ValidateService stage, then recovered by redeploying a working artifact.
Ready to quiz yourself?