Welcome back to the KpolitX tech blog! In the previous articles, we looked at building applications and working with backend technologies. This time, we are moving one step further into the world of DevOps and cloud deployment.
Building an application is only one part of software development. The more interesting question is: What happens after you push your code to GitHub?
In this project, I built a small Flask application called VaultMart and used it as a practical way to understand how a complete CI/CD pipeline works with GitHub Actions, Docker and AWS.
The final workflow looks like this:
Code Change
↓
Git Push
↓
GitHub Actions
↓
Pytest
↓
Docker Build
↓
Amazon ECR
↓
AWS Systems Manager
↓
Amazon EC2
↓
🎬 Live VaultMart
1. What We Are Building
Instead of looking at GitHub Actions, Docker and AWS as completely separate technologies, this project connects them into one workflow.
The application starts as a simple Flask project running locally. From there, it becomes a Docker image, gets pushed to Amazon ECR, and is eventually deployed to an EC2 server.
Developer
│
▼
GitHub Repository
│
▼
GitHub Actions
│
├── Install dependencies
├── Run tests
└── Build Docker image
│
▼
Amazon ECR
│
▼
AWS Systems Manager
│
▼
Amazon EC2
│
▼
Docker Container
│
▼
VaultMart Application
2. Get the VaultMart Project
If you want to follow the project yourself, start with the project repository and clone it locally.
git clone <your-github-repository-url>
Move into the project directory:
cd VaultMart
You can use the project as a starting point and follow the remaining steps to understand how the CI/CD pipeline is assembled.
3. Understand the Flask Project
The application is intentionally kept simple. The goal is not to build a huge Flask system but to have a working application that can travel through the complete deployment lifecycle.
VaultMart/
│
├── app.py
├── requirements.txt
├── templates/
│ └── index.html
└── tests/
└── test_app.py
The main files have different responsibilities:
| File | Purpose |
|---|---|
| app.py | Flask application |
| requirements.txt | Python dependencies |
| templates/index.html | Application frontend |
| tests/test_app.py | Automated tests |
4. Install the Python Dependencies
Install the packages defined in the project's dependency file:
pip install -r requirements.txt
One important detail here is that the same
requirements.txt file will later be used by both the
CI environment and the Docker image.
5. Run VaultMart Locally
Before introducing automation, the application should work correctly on the development machine.
python app.py
The application should be available at:
http://localhost:5000
A typical Flask development configuration uses:
app.run(host="0.0.0.0", port=5000, debug=True)
Here, 0.0.0.0 allows Flask to listen on available network
interfaces, while port 5000 is used by the application.
6. Add Automated Testing with Pytest
Manual testing becomes inefficient as a project grows. One of the first CI/CD principles is therefore simple: test the application automatically.
The test file is located at:
tests/
└── test_app.py
Run the test suite locally:
pytest
7. Initialize Git
Once the application works locally, initialize a Git repository:
git init
Check the repository:
git status
8. Create a .gitignore File
Local environments contain files that should not be committed to the repository.
A basic Python .gitignore could contain:
venv/
__pycache__/
*.pyc
.env
This prevents virtual environments, Python cache files and local environment configuration from accidentally being committed.
9. Create the First Commit
git add .
git commit -m "Initial VaultMart application"
10. Connect the GitHub Repository
Connect the local repository to GitHub:
git remote add origin <repository-url>
git branch -M main
git push -u origin main
The source code is now available on GitHub and ready for automation.
11. Why Docker?
The next challenge is making sure the application can run consistently outside the development machine.
Docker packages the application together with its runtime environment and dependencies:
Application
+
Python
+
Dependencies
+
Configuration
│
▼
Docker Image
This image can then be moved between environments without rebuilding the application from scratch.
12. Create the Dockerfile
Create a file named:
Dockerfile
Add the following:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Understanding the Dockerfile
- FROM provides the Python base image.
- WORKDIR creates the working directory inside the container.
- COPY moves project files into the image.
- RUN installs Python dependencies.
- EXPOSE documents the application port.
- CMD starts the Flask application.
13. Build the Docker Image
Build the image locally:
docker build -t VaultMartappimage .
Check the available images:
docker images
14. Run VaultMart Inside Docker
Run the container:
docker run -p 5000:5000 VaultMartappimage
The port mapping means:
Host Port 5000
│
▼
Container Port 5000
│
▼
Flask Application
Open http://localhost:5000 and verify that the application
works inside the container.
15. Create the GitHub Actions Workflow
Now we can start automating the process.
Create the following directory:
.github/
└── workflows/
└── ci.yml
16. Configure the Workflow Trigger
The workflow can be triggered whenever code is pushed to the
main branch.
name: VaultMart CI/CD
on:
push:
branches:
- main
17. Build the CI Pipeline
The first part of the workflow is continuous integration. It should validate the code before deployment.
Checkout the Code
- name: Checkout code
uses: actions/checkout@v4
Set Up Python
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
Install Dependencies
- name: Install dependencies
run: pip install -r requirements.txt
Run Pytest
- name: Run tests
run: pytest
GitHub Push
↓
Checkout
↓
Python Setup
↓
Install Dependencies
↓
Pytest
↓
Tests Passed
18. Configure AWS IAM for GitHub Actions
GitHub Actions needs permission to communicate with AWS services such as Amazon ECR and Systems Manager.
For this project, a dedicated IAM identity was used rather than using the AWS root account.
Why IAM Matters
IAM determines which AWS resources an identity can access and which operations it is allowed to perform.
GitHub Actions
↓
IAM Identity
↓
AWS Permissions
↓
ECR / SSM / Other Services
19. Create AWS Access Credentials
After creating the CI/CD IAM identity, the required credentials can be configured for the GitHub workflow.
These credentials should never be placed directly inside the workflow YAML file.
20. Store AWS Credentials in GitHub Secrets
GitHub repository secrets can be used to keep sensitive values outside the source code.
Typical values for this project include:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYECR_REPOSITORY
21. Create the Amazon ECR Repository
Amazon Elastic Container Registry, or ECR, provides a private registry for storing Docker images.
GitHub Actions
↓
Docker Image
↓
Amazon ECR
↓
VaultMart:latest
Create a repository for the VaultMart container image and keep its URI available for the deployment workflow.
22. Authenticate GitHub Actions with AWS
The workflow uses the AWS credentials stored in GitHub Secrets to authenticate with AWS.
GitHub Actions
↓
GitHub Secrets
↓
AWS Authentication
↓
Amazon ECR
The credentials remain outside the workflow source itself.
23. Authenticate Docker with ECR
After AWS authentication, Docker also needs to authenticate with the ECR registry before it can push an image.
AWS Authentication
↓
ECR Login
↓
Docker Authenticated
↓
Image Can Be Pushed
24. Build the Docker Image in CI
After the tests pass, GitHub Actions builds the production image.
docker build -t VaultMart .
The image is created inside the GitHub Actions runner.
25. Tag the Docker Image
Docker needs to know which ECR repository the image belongs to. Therefore, the local image is tagged with the ECR repository URI.
docker tag VaultMart:latest $ECR_REPOSITORY:latest
26. Push the Image to ECR
Once the image has been tagged, push it to Amazon ECR:
docker push $ECR_REPOSITORY:latest
The complete image publishing flow is:
GitHub Actions
↓
Docker Build
↓
Docker Image
↓
Docker Tag
↓
ECR Login
↓
Docker Push
↓
Amazon ECR
27. Create the EC2 Production Server
The next component is the EC2 instance that will run the live VaultMart container.
AWS
│
└── EC2
│
└── Linux Server
│
└── Docker
│
└── VaultMart
28. Configure the EC2 Security Group
The EC2 Security Group controls inbound network traffic. For a basic version of this project, the required ports are:
| Protocol | Port | Purpose |
|---|---|---|
| TCP | 22 | Initial SSH administration |
| TCP | 5000 | VaultMart application |
29. Install Docker on EC2
Connect to the Linux server and install Docker.
sudo apt update
sudo apt install docker.io -y
sudo systemctl enable --now docker
docker --version
30. Create an IAM Role for EC2
The EC2 server also needs permission to communicate with AWS. Instead of storing long-term AWS access keys on the server, an IAM Role can be attached directly to the instance.
This is different from the IAM identity used by GitHub Actions.
GitHub Actions
↓
IAM Identity
↓
AWS Services
EC2
↓
IAM Role
↓
AWS Services
31. EC2 IAM Permissions
The project uses AWS-managed policies for the services required by the EC2 deployment environment.
| Policy | Purpose |
|---|---|
| AmazonEC2ContainerRegistryReadOnly | Allows EC2 to retrieve container images from ECR |
| AmazonSSMManagedInstanceCore | Allows Systems Manager to communicate with the instance |
| CloudWatchAgentServerPolicy | Supports CloudWatch monitoring and log collection |
32. Attach the IAM Role to EC2
In the AWS Console, the instance IAM role can be associated with the EC2 instance through its security settings.
EC2 ↓ Instance ↓ Security ↓ Modify IAM Role ↓ Select VaultMart Role
Once attached, EC2 can communicate with the required AWS services without storing long-term AWS access keys directly on the server.
33. Configure AWS Systems Manager
AWS Systems Manager, commonly called SSM, can be used to execute commands remotely on the EC2 instance.
This means the deployment pipeline does not need to manually connect to the EC2 server through SSH for every application update.
GitHub Actions
↓
AWS Systems Manager
↓
EC2
↓
Deployment Commands
After configuring the IAM role, verify that the EC2 instance appears as a managed node in Systems Manager and reports an online state.
34. Retrieve the Image on EC2
The EC2 server now needs to retrieve the image produced by the CI/CD pipeline.
Amazon ECR
↓
ECR Authentication
↓
Docker Pull
↓
Latest VaultMart Image
The important concept is that the server is using the same container image that was built and published by the CI/CD pipeline.
35. Run the VaultMart Container
The updated image can now be used to run the application.
docker run -d \
--name VaultMart-container \
-p 5000:5000 \
<ECR-REPOSITORY>:latest
The application uses port 5000, with the container mapping
represented as:
5000:5000
36. Connect Everything Together
At this point, the individual technologies are no longer isolated. They form a complete delivery pipeline.
Developer
│
│ Code Change
▼
GitHub
│
│ Push
▼
GitHub Actions
│
├── Checkout
├── Python Setup
├── Install Dependencies
├── Pytest
└── Docker Build
│
▼
Amazon ECR
│
│ Docker Image
▼
AWS Systems Manager
│
│ Remote Command
▼
Amazon EC2
│
├── ECR Authentication
├── Docker Pull
└── Updated Container
│
▼
🎬 Live VaultMart
37. Test the Complete Automation
Now comes the most important practical test: make a change to the application and see whether the pipeline handles it automatically.
Make a Code Change
For example, update a piece of text in the VaultMart interface:
<p>🚀 Latest CI/CD Deployment Test</p>
Commit the Change
git add .
git commit -m "Update VaultMart"
Push to GitHub
git push origin main
That single push should trigger the GitHub Actions workflow.
38. Watch GitHub Actions
Open the repository on GitHub and navigate to:
GitHub Repository
↓
Actions
↓
VaultMart Workflow
The pipeline should move through stages similar to:
Checkout ↓ Install Dependencies ↓ Pytest ↓ Docker Build ↓ AWS Authentication ↓ ECR Push ↓ SSM Deployment
39. Verify the Deployment
After the workflow finishes successfully, the updated Docker image should be available in ECR and the deployment command should have been executed on the EC2 instance.
The EC2 server then runs the updated VaultMart container.
Code Change
↓
Git Push
↓
Automated Pipeline
↓
Updated Docker Image
↓
EC2 Deployment
↓
Updated Container
↓
Live Application
40. Final Architecture
The entire project can be summarized with the following architecture:
┌──────────────────────────┐
│ Developer │
│ │
│ Git Push │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ GitHub │
│ Source Code │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ GitHub Actions │
│ │
│ • Checkout │
│ • Test │
│ • Docker Build │
│ • AWS Authentication │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ Amazon ECR │
│ Docker Image │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ AWS Systems Manager │
│ Remote Commands │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ AWS EC2 │
│ │
│ Docker │
│ ↓ │
│ VaultMart Container │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ 🎬 Live VaultMart │
└──────────────────────────┘
41. How IAM Fits Into the Architecture
One of the most useful concepts from this project is that there are different AWS access paths for GitHub Actions and EC2.
GitHub Actions → AWS
GitHub Actions
↓
GitHub Secrets
↓
IAM Identity
↓
AWS Services
EC2 → AWS
EC2 ↓ IAM Role ↓ AWS Services
Keeping these access paths separate makes the architecture easier to reason about and avoids placing long-term AWS credentials directly on the application server.
42. What I Learned From This Project
Before working on this project, it was easy to think about DevOps tools individually:
- Git
- Docker
- GitHub Actions
- AWS
- EC2
The more valuable part was understanding how they connect.
- Git manages code changes.
- GitHub hosts the source code.
- Pytest validates the application.
- Docker packages the application.
- GitHub Actions automates the workflow.
- GitHub Secrets protect workflow credentials.
- IAM controls AWS permissions.
- ECR stores Docker images.
- EC2 provides the compute environment.
- SSM enables remote deployment commands.
- Docker on EC2 runs the application.
43. The Biggest Takeaway
The most important thing I took from this project was not memorizing another Docker command or another YAML configuration.
It was understanding the complete lifecycle of an application:
Write Code
↓
Test Code
↓
Commit Code
↓
Push Code
↓
Run CI
↓
Build Docker Image
↓
Store Image
↓
Deploy Image
↓
Run Container
↓
Live Application
A simple:
git push
can become the starting point for an automated journey from source code to a running application.
44. Final Thoughts
VaultMart started as a simple Flask application, but the real learning came from everything around it.
By connecting local development, Git, automated testing, Docker, GitHub Actions, AWS IAM, ECR, EC2 and Systems Manager, the project became a practical introduction to the DevOps lifecycle.
The goal was not simply to deploy a Flask application. The goal was to understand how software can move from code to production through an automated and repeatable process.
45. Explore the Project
The complete source code and CI/CD implementation can be made available through the project's GitHub repository.
👉 GitHub Repository:
VaultMart-devops-cicd
If you are learning DevOps, try rebuilding the pipeline yourself instead of only reading through the commands. The real understanding comes from seeing each component work together.
Comments
Post a Comment