From broken builds to a fully automated deployment pipeline.
As part of my DevOps learning journey, I wanted to build something practical, realistic, and close to production workflows β not just follow tutorials.
So I built a two-tier web application and automated its entire build, test, and deployment lifecycle using Jenkins, Docker, and Docker Compose.
This article walks through:
β’ The architecture I built
β’ The CI/CD pipeline design
β’ Real issues I faced (and fixed)
β’ Key DevOps learnings
π§± Project Overview
The goal was simple but important:
Automate everything from code commit to deployment β with no manual steps.
Tech Stack
β’ Frontend / API: Flask (Python)
β’ Database: MySQL
β’ CI/CD: Jenkins (Pipeline as Code)
β’ Containerization: Docker
β’ Orchestration: Docker Compose
β’ Version Control: GitHub
π Architecture Overview
The application follows a classic two-tier architecture:
β’ Web tier: Flask application
β’ Data tier: MySQL database
β’ Both services run in separate Docker containers
β’ Managed via Docker Compose
GitHub
β
Jenkins Pipeline
β
Docker Build
β
Tests
β
Docker Compose Deploy
π³ Docker & Docker Compose Setup
Docker Compose is used to define and run multiple containers together.
Services:
β’ db: MySQL 8 container
β’ web: Flask application container
Key highlights:
β’ Database initialization using init.sql
β’ Environment variables for DB connectivity
β’ Service dependency (web depends on db)
πΈ Image: docker-compose.yml (services definition)
π Flask Application
The Flask app exposes simple endpoints:
β’ / β health check
β’ /users β fetches data from MySQL
The database connection is handled using environment variables, making the app environment-agnostic.
πΈ Image: db.py (MySQL connection using env variables)
πΈ Image: app.py (Flask routes and app runner)
This design ensures:
β’ No hardcoded credentials
β’ Easy containerization
β’ Smooth CI/CD execution
π Dockerfile (Application Image)
The Flask app is containerized using a simple, optimized Dockerfile:
β’ Lightweight base image (python:3.9-slim)
β’ Dependency installation via requirements.txt
β’ Exposes port 5000
β’ Runs the app using CMD
πΈ Image: Dockerfile
βοΈ Jenkins CI/CD Pipeline
This is where the real DevOps work happens.
The pipeline is written using Jenkins Declarative Pipeline and stored as a Jenkinsfile in the repository.
Pipeline Stages:
1. Checkout β Pull source code from GitHub
2. Build Image β Build Docker images using Docker Compose
3. Run Tests β Start DB container and validate application
4. Deploy β Bring up all services using Docker Compose
πΈ Image: Jenkinsfile (pipeline stages)
π Jenkins Stage View (Pipeline Execution)
The Jenkins Stage View clearly shows:
β’ Which stage failed
β’ How long each stage took
β’ When the pipeline finally turned green
This helped massively during debugging.
πΈ Image: Jenkins Stage View (failed β successful build)
𧨠Real Issues I Faced (and Fixed)
This project was not smooth, and thatβs where the learning came from.
π΄ Issue 1: Jenkins couldnβt find Jenkinsfile
β’ Root cause: Case sensitivity
β’ jenkinsfile β Jenkinsfile
β Fixed by forcing correct filename in Git
π΄ Issue 2: docker: not found in Jenkins
β’ Jenkins was running inside a container
β’ Docker CLI wasnβt available inside Jenkins
β Installed Docker CLI inside Jenkins container
π΄ Issue 3: Docker Compose v1 vs v2 mismatch
β’ Jenkins environment only had docker-compose
β’ Pipeline was using docker compose
β Aligned Jenkinsfile with available tooling
π΄ Issue 4: Permission denied on /var/run/docker.sock
β’ Jenkins user couldnβt talk to Docker daemon
β’ Linux socket permission issue
β Fixed by adjusting Docker socket permissions (for local CI)
β Final Result
After resolving all issues:
β’ β Fully automated CI/CD pipeline
β’ β Build β Test β Deploy without manual steps
β’ β Dockerized multi-container application
β’ β Jenkins pipeline running clean and green
πΈ Image: Successful Jenkins pipeline run
π Key Learnings
This project taught me far more than any tutorial:
β’ DevOps is more about debugging systems than writing YAML
β’ Linux fundamentals matter a lot
β’ CI/CD environments behave differently than local machines
β’ Logs are your best debugging tool
β’ Small permission issues can break entire pipelines
β’ Automation only works when every layer is understood
π Source Code
GitHub Repository:
π https://github.com/developedbyviv/Two-tier-Architecture-Application






Top comments (1)
Jenkins in 2025... you are funny π