From scp-and-restart to CodePipeline: CI/CD for a Small Flask Team

AlexJan 28, 2024 1 minDevOps
From scp-and-restart to CodePipeline: CI/CD for a Small Flask Team

A while back, I had a Flask project heading to production. The team was only five or six people, but our release frequency was high enough that the old "scp and restart" routine was no longer sustainable. I spent a week wiring up CodePipeline, CodeBuild, and CodeDeploy on AWS. I hit a few snags along the way, but once everything was running, it genuinely saved me a lot of hassle. Here's the overall design and the specific steps I took, in case it helps.

What Each Service Does

Let's start with the division of labor so the workflow makes sense later.

  • **CodePipeline**: A fully managed continuous delivery orchestrator. You define the "source → build → test → deploy" chain, and it drives each step in sequence. Beyond native AWS services, it also integrates with third-party code sources like GitHub and Bitbucket.
  • **CodeBuild**: Fully managed continuous integration. You hand it a build script, and it pulls the code, runs compilation, runs tests, and produces the artifact. No need to maintain your own build servers.
  • **CodeDeploy**: An automated deployment executor. It pushes the software package produced in the previous step to your designated targets—Amazon EC2, AWS Fargate, and AWS Lambda are all supported.

My take: used in isolation, each of these three has limited value. What makes them truly useful is that CodePipeline orchestrates them into a single pipeline. You just declare the stages in the console (or in CloudFormation), and it handles the scheduling for you.

How I Actually Wired It Together

My pipeline has four stages, and the mapping is straightforward:

  • Source stage: Pulls changes from the code repository and triggers the pipeline.
  • Build stage: Handled by CodeBuild. I set up a CodeBuild project with a buildspec that runs `pip install -r requirements.txt` followed by the pytest suite. CodeBuild automatically pulls the source, executes the script, and packages the output into a tar archive.
  • Test stage: Runs within the same CodeBuild project as the build; I didn't split it out separately.
  • Deploy stage: Once CodePipeline detects that the build artifact is ready, it automatically triggers CodeDeploy to push the package to the target EC2 instance.

I hit a snag here: on the first deployment, Flask didn't come up. After some troubleshooting, I found that the path to `requirements.txt` was wrong inside CodeDeploy's extraction directory, which caused `pip install` to install into the wrong Python environment. The fix was to explicitly specify the working directory and the start command in the lifecycle events (`AfterInstall` and `ApplicationStart`) in `appspec.yml`. That resolved it, and I haven't seen the issue since.

The Approval Step: Why I Added It and How

Full automation sounds great, but for production I've always felt that a human in the loop is non-negotiable. So I inserted an approval action between the build and deploy stages:

  • Add an action of type Approval in CodePipeline, pointing to an SNS topic.
  • Every time the pipeline reaches this step, the designated approver receives an Amazon SNS notification email containing a change summary and a link to the pipeline details.
  • When the approver clicks "Approve," the pipeline continues and CodeDeploy begins the deployment. If they click "Reject," the pipeline halts immediately, giving the team time to investigate or roll back the code.

In practice, this step has been especially useful for compliance audits—every deployment has a clear record of who approved it and when, so there's no need to dig through logs afterward.

How It Feels Once Everything Is Running

walking

Once the whole setup stabilized, going from `git push` to a production update no longer required any manual intervention (other than that one approval click). Delivery cycles did shrink, and because every build runs the same set of scripts, environment drift is essentially a thing of the past. My overall impression of AWS's DevOps toolchain: each service on its own is "good enough," but it's only once they're orchestrated together that you get a true closed loop—agility and control, both in play at the same time.

References

  • Unifying the DevOps Process: The Key to Vendor Collaboration
  • Achieving Seamless Integration Between GitLab and AWS CodeCommit
  • Exploring the DevOps Journey: Harmonizing Automated Operations, Governance, and Business Operations
B
About the author · Alex

I'm Alex — 12+ years of software architecture, focused on AI private deployment, DevOps, and cloud-native design. This is where I share first-line technical practice and career growth.

Subscribe to updates

Stay updated with the latest insights on AI, DevOps, and cloud architecture.

Subscribe via RSS