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

- Published: 2024-01-28 · Category: DevOps

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.

## 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.

## 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.

## How It Feels Once Everything Is Running

![walking](https://aicyber.de5.net/sites/blog-252d6582/shared/images/screenshot-20240128-devops-aws-codepipeline-example.webp)

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

