Deploy Python Apps with AWS CodeDeploy
Deploy Python apps with AWS CodeDeploy — AWS Cloud & DevOps with Python.
Focus: deploy python apps with aws codedeploy
You've built a Python app, it tests green locally, and you're ready to ship. But then comes the dread: manually SSH-ing into servers, pulling code, restarting services, and praying nothing breaks. It's slow, error-prone, and doesn't scale beyond one box. This is the pain that AWS CodeDeploy solves — it automates the deployment of your Python app to EC2 instances (and other compute services) with rolling updates, automatic rollbacks, and zero downtime. By the end of this lesson, you'll be able to deploy your Python app to EC2 using CodeDeploy and understand when it beats simpler tools like scp or rsync.
The Problem This Lesson Solves
Manual deployment is the enemy of reliability. When you deploy by hand, you risk:
- Inconsistency: You forget a step, or you run it slightly differently on each server.
- Downtime: You stop the service, upload files, and restart — users see errors during that window.
- No rollback: If something breaks, you have to scramble to revert changes without losing data.
- No audit trail: Who deployed what, and when? You have no record.
As your app grows from one EC2 instance to a fleet behind a load balancer, manual deployment becomes impossible. AWS CodeDeploy automates the entire process: it packages your application, deploys it to each instance, runs lifecycle hooks (like stopping/starting your service), and can automatically roll back if health checks fail.
Core Concept / Mental Model
Think of AWS CodeDeploy as a conductor for your application's release. You provide the score (the AppSpec file and the archive), and CodeDeploy directs the orchestra (your EC2 instances) to play it in perfect sync.
Here are the key terms you'll encounter:
- Application: A logical name for what you're deploying (e.g.,
my-python-app). - Deployment Group: A set of targets (EC2 instances, ASGs, Lambda functions) with associated settings like deployment type and rollback triggers.
- Revision: A specific version of your app — typically a ZIP or TAR file stored in S3 or GitHub, along with an appspec.yml file.
- AppSpec file: The heart of CodeDeploy. It's a YAML (or JSON) file that tells CodeDeploy what to do during each lifecycle event.
- Lifecycle Events: A sequence of hooks (BeforeInstall, AfterInstall, ApplicationStart, etc.) that CodeDeploy runs on each instance. This is where you run your scripts to stop/start the service.
Pro tip: The AppSpec file is your deployment recipe. It defines both file mappings (what to copy where) and hooks (scripts to run at specific points). Getting this right is 80% of the battle.
How It Works Step by Step
Let's trace a typical deployment:
- Package your app — Create a ZIP archive containing your Python code, an
appspec.yml, and any scripts (e.g.,scripts/stop_server.sh,scripts/start_server.sh). - Upload the revision to S3 (or push to GitHub).
- Create a CodeDeploy application and a deployment group that targets your EC2 instances (by tags, ASG name, or individual IDs).
- Trigger a deployment — either manually via CLI/console, or automatically through a CI/CD pipeline (CodePipeline, Jenkins).
- CodeDeploy installs the agent on each target instance. The agent polls for deployments and executes the lifecycle hooks.
- Lifecycle hooks run — For example:
-
BeforeInstall: Maybe stop the previous version? (Though usually you stop inApplicationStop.) -AfterInstall: Install dependencies (pip install), set permissions, etc. -ApplicationStart: Start your service (systemctl start or nohup python app.py). -ValidateService: Run a health check (curl localhost:8000/health) to confirm the app is up. - CodeDeploy reports success/failure — If any hook fails, the deployment stops (or rolls back, if configured).
Hands-on Walkthrough
Let's deploy a simple Flask app to a single EC2 instance. We'll use the AWS CLI and a minimal AppSpec.
Step 1: Create a simple Flask app
Create a directory flask-app/ with this app.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return jsonify({"message": "Hello from CodeDeploy!"})
@app.route('/health')
def health():
return jsonify({"status": "healthy"}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Step 2: Add lifecycle scripts
Create scripts/start_server.sh and scripts/stop_server.sh:
#!/bin/bash
# start_server.sh
echo "Starting Flask app..."
source /home/ubuntu/myenv/bin/activate
cd /home/ubuntu/app
nohup python app.py > /home/ubuntu/app/app.log 2>&1 &
echo "Started"
#!/bin/bash
# stop_server.sh
pkill -f 'python app.py' || true
echo "Stopped"
Make them executable: chmod +x scripts/*.sh.
Step 3: Write appspec.yml
version: 0.0
os: linux
files:
- source: /app
destination: /home/ubuntu/app
permissions:
- object: /home/ubuntu/app
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStop:
- location: scripts/stop_server.sh
runas: ubuntu
AfterInstall:
- location: scripts/install_dependencies.sh
runas: ubuntu
ApplicationStart:
- location: scripts/start_server.sh
runas: ubuntu
ValidateService:
- location: scripts/validate_service.sh
runas: ubuntu
Notice we referenced install_dependencies.sh and validate_service.sh — let's create them:
#!/bin/bash
# install_dependencies.sh
cd /home/ubuntu/app
python3 -m venv myenv
source myenv/bin/activate
pip install flask
#!/bin/bash
# validate_service.sh
sleep 5
curl -f http://localhost:8000/health || exit 1
Step 4: Package and upload to S3
zip -r flask-app.zip . -x 'myenv/*'
aws s3 cp flask-app.zip s3://my-deployment-bucket/flask-app.zip
Step 5: Create CodeDeploy app and deployment group
aws deploy create-application --application-name FlaskApp
aws deploy create-deployment-group \
--application-name FlaskApp \
--deployment-group-name Production \
--ec2-tag-filters Key=Name,Value=MyWebServer,Type=KEY_AND_VALUE \
--service-role-arn arn:aws:iam::123456789012:role/CodeDeployRole
Step 6: Trigger deployment
aws deploy create-deployment \
--application-name FlaskApp \
--deployment-group-name Production \
--s3-location bucket=my-deployment-bucket,key=flask-app.zip,bundleType=zip
Monitor with aws deploy get-deployment --deployment-id d-EXAMPLE.
Expected output: The deployment succeeds, and curl http://<ec2-public-ip>:8000/health returns {"status": "healthy"}.
Compare Options / When to Choose What
| Tool | Best for | Deployment style | Rollback | Learning curve |
|---|---|---|---|---|
| AWS CodeDeploy | EC2/on-prem with complex lifecycle | Rolling, blue/green | Automatic | Medium |
| Elastic Beanstalk | Quick PaaS deploys | Managed | Easy | Low |
| CodePipeline + Lambda | Serverless apps | Push-based | Manual | Medium |
| GitHub Actions + SSH | Simple single-server | Manual | Manual | Low |
Pro tip: Choose CodeDeploy when you need fine-grained control over lifecycle events, target multiple EC2 instances, or require automatic rollback. For a single server and no room for complexity, a simple rsync + systemctl restart via SSH might be enough.
Troubleshooting & Edge Cases
- Permission denied on hooks: Ensure scripts have
runasset to a user with execute permissions (e.g.,ubuntu), and setchmod +x. - Script fails silently: The Agent logs are in
/var/log/aws/codedeploy-agent/. Check them first. - pip install fails inside hook: The shell environment may differ — source the virtualenv explicitly, and set
PATH. - ValidateService fails: Increase the
sleepor make the health check more tolerant. Usecurl -fand exit 1 on failure. - Rollback triggers: Set them in the deployment group to avoid waiting for a human.
- Multiple instances: Use deployment style
BLUE_GREENorALL_AT_ONCE(with care) vs. rolling.
What You Learned & What's Next
You now understand how to deploy Python apps with AWS CodeDeploy: you've seen the core components (AppSpec, lifecycle events, deployment groups), completed a hands-on deployment of a Flask app, compared it with alternatives, and learned how to debug common issues. You can now: explain the core idea behind CodeDeploy, complete a practical exercise, and decide when to use it. Next in the track, you'll learn how to automate the whole pipeline — from git push to production — using AWS CodePipeline, which will call CodeDeploy as its final step. That's the true power of DevOps: one push, zero manual steps.
Practice recap
To reinforce what you learned, modify the sample Flask app to include a database migration step in the AfterInstall hook, then deploy it again. Then, intentionally break the ValidateService script and observe the rollback behavior you configured. This hands-on exercise will make you comfortable with the deployment lifecycle and disaster recovery.
Common mistakes
- Forgetting to make lifecycle scripts executable (
chmod +x) — CodeDeploy will fail with 'ScriptDoesNotExist' or 'PermissionDenied'. - Not sourcing the virtual environment in start scripts, causing
ModuleNotFoundErrorwhen the app runs. - Using
pkill -f python app.pywithout|| true— the hook fails if the process isn't running, stopping the deployment. - Ignoring the CodeDeploy agent logs (
/var/log/aws/codedeploy-agent/) when debugging — they contain the exact script output. - Setting
ValidateServiceto run immediately without waiting — the app may not be ready, causing a false failure.
Variations
- Use
BLUE_GREENdeployment type instead ofALL_AT_ONCEto minimize downtime and allow instant rollback. - Store your revision in GitHub instead of S3 — CodeDeploy can deploy directly from a GitHub repository with webhooks.
- Use AppSpec JSON instead of YAML if you prefer programmatic generation.
Real-world use cases
- Deploying a Flask microservice to a fleet of EC2 instances behind a load balancer, with rolling updates and automatic rollback if health checks fail.
- Releasing a Django web app to staging and production environments using the exact same AppSpec, differing only in deployment group settings.
- Automating a CI/CD pipeline where CodePipeline triggers CodeDeploy after tests pass, giving every commit a reproducible, auditable deployment.
Key takeaways
- CodeDeploy automates deployments via an AppSpec file that defines file mappings and lifecycle hooks.
- Lifecycle events (BeforeInstall, AfterInstall, ApplicationStart, ValidateService) give you fine-grained control over the rollout.
- Revisions are usually ZIP files stored in S3 (or GitHub); the CodeDeploy agent pulls and applies them.
- Always configure rollback triggers and set
ValidateServicewith a health check to catch broken deploys. - Compare CodeDeploy with Elastic Beanstalk or simple SSH scripts to match the right tool to your needs.
- Debugging hinges on reading the agent logs at
/var/log/aws/codedeploy-agent/.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.