Deploy Python Apps with AWS CodeDeploy

Deploy Python apps with AWS CodeDeploy — AWS Cloud & DevOps with Python.

Focus: deploy python apps with aws codedeploy

Sponsored

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:

  1. 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).
  2. Upload the revision to S3 (or push to GitHub).
  3. Create a CodeDeploy application and a deployment group that targets your EC2 instances (by tags, ASG name, or individual IDs).
  4. Trigger a deployment — either manually via CLI/console, or automatically through a CI/CD pipeline (CodePipeline, Jenkins).
  5. CodeDeploy installs the agent on each target instance. The agent polls for deployments and executes the lifecycle hooks.
  6. Lifecycle hooks run — For example: - BeforeInstall: Maybe stop the previous version? (Though usually you stop in ApplicationStop.) - 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.
  7. 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 runas set to a user with execute permissions (e.g., ubuntu), and set chmod +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 sleep or make the health check more tolerant. Use curl -f and exit 1 on failure.
  • Rollback triggers: Set them in the deployment group to avoid waiting for a human.
  • Multiple instances: Use deployment style BLUE_GREEN or ALL_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 ModuleNotFoundError when the app runs.
  • Using pkill -f python app.py without || 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 ValidateService to run immediately without waiting — the app may not be ready, causing a false failure.

Variations

  1. Use BLUE_GREEN deployment type instead of ALL_AT_ONCE to minimize downtime and allow instant rollback.
  2. Store your revision in GitHub instead of S3 — CodeDeploy can deploy directly from a GitHub repository with webhooks.
  3. 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 ValidateService with 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/.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.