# Complete End-to-End DevOps Project Guide ## Building a Production-Ready Flask Application with Modern DevOps Practices --- ## Table of Contents 1. [Project Overview and Architecture](#project-overview-and-architecture) 2. [Technology Stack Analysis](#technology-stack-analysis) 3. [Project Structure and Organization](#project-structure-and-organization) 4. [Phase 1: Development Environment Setup](#phase-1-development-environment-setup) 5. [Phase 2: Application Development](#phase-2-application-development) 6. [Phase 3: Containerization with Docker](#phase-3-containerization-with-docker) 7. [Phase 4: Version Control and Git Workflow](#phase-4-version-control-and-git-workflow) 8. [Phase 5: CI/CD Pipeline Implementation](#phase-5-cicd-pipeline-implementation) 9. [Phase 6: Infrastructure as Code with Terraform](#phase-6-infrastructure-as-code-with-terraform) 10. [Phase 7: Kubernetes Deployment](#phase-7-kubernetes-deployment) 11. [Phase 8: Monitoring and Observability](#phase-8-monitoring-and-observability) 12. [Phase 9: Security Implementation](#phase-9-security-implementation) 13. [Phase 10: Documentation and Presentation](#phase-10-documentation-and-presentation) 14. [Publishing Strategy for LinkedIn and GitHub](#publishing-strategy-for-linkedin-and-github) --- ## Project Overview and Architecture ### What We're Building You're creating a production-grade DevOps pipeline for a Flask web application that demonstrates enterprise-level practices. This project will showcase your ability to implement modern software delivery methodologies, infrastructure automation, and operational excellence. ### Why This Project Matters In today's software industry, DevOps practices are essential for delivering reliable, scalable applications. This project demonstrates your understanding of the complete software delivery lifecycle, from code development to production deployment and monitoring. ### Architecture Overview ``` Developer → Git → GitHub → GitHub Actions → Docker → Kubernetes → GCP → Monitoring ↓ ↓ ↓ ↓ ↓ ↓ ↓ Local IDE → Version → CI/CD Pipeline → Container → Orchestration → Cloud → Observability ``` --- ## Technology Stack Analysis ### Core Technologies (Enhanced from Your List) #### Application Layer - **Python 3.11+**: Modern Python version with performance improvements - **Flask 2.3+**: Lightweight web framework with extensive ecosystem - **Gunicorn**: WSGI HTTP Server for production deployment - **Flask-CORS**: Cross-Origin Resource Sharing support - **Flask-Limiter**: Rate limiting for API protection #### Development and Testing - **pytest**: Industry-standard testing framework - **pytest-cov**: Code coverage reporting - **black**: Code formatting for consistency - **flake8**: Linting for code quality - **mypy**: Static type checking - **pre-commit**: Git hooks for quality gates #### Containerization - **Docker**: Container platform - **Docker Compose**: Multi-container orchestration - **Distroless Images**: Minimal, secure base images #### Version Control and Collaboration - **Git**: Distributed version control - **Git Flow**: Branching strategy - **Conventional Commits**: Standardized commit messages - **Semantic Versioning**: Version management strategy #### CI/CD Pipeline - **GitHub Actions**: Automation platform - **GitHub Container Registry**: Private image registry - **Dependabot**: Automated dependency updates - **CodeQL**: Security analysis #### Infrastructure and Orchestration - **Terraform**: Infrastructure as Code - **Google Cloud Platform**: Cloud provider - **Google Kubernetes Engine (GKE)**: Managed Kubernetes - **Helm**: Kubernetes package manager - **Kustomize**: Kubernetes configuration management #### Monitoring and Observability - **Prometheus**: Metrics collection - **Grafana**: Visualization and dashboards - **Loki**: Log aggregation - **Jaeger**: Distributed tracing - **AlertManager**: Alert routing and management #### Security - **Trivy**: Vulnerability scanning - **Google Secret Manager**: Secrets management - **Network Policies**: Kubernetes security - **RBAC**: Role-based access control --- ## Project Structure and Organization ### Directory Structure ``` flask-devops-project/ ├── .github/ │ ├── workflows/ │ │ ├── ci.yml │ │ ├── cd-dev.yml │ │ ├── cd-staging.yml │ │ └── cd-prod.yml │ ├── ISSUE_TEMPLATE/ │ └── PULL_REQUEST_TEMPLATE.md ├── app/ │ ├── __init__.py │ ├── main.py │ ├── models/ │ ├── routes/ │ ├── utils/ │ └── templates/ ├── tests/ │ ├── unit/ │ ├── integration/ │ └── e2e/ ├── docker/ │ ├── Dockerfile │ ├── Dockerfile.dev │ └── docker-compose.yml ├── k8s/ │ ├── base/ │ ├── overlays/ │ │ ├── dev/ │ │ ├── staging/ │ │ └── prod/ │ └── helm-chart/ ├── terraform/ │ ├── modules/ │ ├── environments/ │ │ ├── dev/ │ │ ├── staging/ │ │ └── prod/ │ └── global/ ├── monitoring/ │ ├── prometheus/ │ ├── grafana/ │ └── loki/ ├── docs/ │ ├── architecture/ │ ├── deployment/ │ └── api/ ├── scripts/ │ ├── setup.sh │ ├── deploy.sh │ └── test.sh ├── .env.example ├── .gitignore ├── .pre-commit-config.yaml ├── requirements.txt ├── requirements-dev.txt ├── Pipfile ├── README.md ├── CHANGELOG.md ├── CONTRIBUTING.md └── LICENSE ``` --- ## Phase 1: Development Environment Setup ### Purpose and Objectives Setting up a consistent, reproducible development environment is crucial for maintaining code quality and ensuring that all team members work with the same tools and configurations. This phase establishes the foundation for all subsequent development work. ### Technologies and Tools - **Python 3.11+**: Latest stable version with performance improvements - **Poetry/Pipenv**: Dependency management and virtual environments - **Pre-commit hooks**: Automated code quality checks - **IDE configuration**: VS Code or PyCharm setup ### Industry Best Practices Modern development environments should be containerized and version-controlled to ensure consistency across different machines and team members. The principle of "infrastructure as code" applies to development environments as well. ### Step-by-Step Implementation #### Task 1.1: System Prerequisites Create a file named `docs/setup/system-requirements.md`: ```markdown # System Requirements ## Required Software - Python 3.11+ - Docker Desktop - Git - Google Cloud SDK - Terraform - kubectl - Helm ## Installation Commands ### macOS (using Homebrew) brew install python@3.11 docker git google-cloud-sdk terraform kubectl helm ### Ubuntu/Debian # Python 3.11 sudo apt update sudo apt install python3.11 python3.11-venv python3.11-pip # Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # Additional tools # Follow official installation guides for each tool ``` #### Task 1.2: Python Environment Setup Create `scripts/setup-dev.sh`: ```bash #!/bin/bash # Development environment setup script set -e echo "Setting up Flask DevOps Project development environment..." # Create virtual environment python3.11 -m venv venv source venv/bin/activate # Upgrade pip pip install --upgrade pip # Install Poetry for dependency management pip install poetry # Install dependencies poetry install --with dev # Install pre-commit hooks pre-commit install # Create environment files cp .env.example .env.dev cp .env.example .env.test echo "Development environment setup complete!" echo "Activate with: source venv/bin/activate" ``` #### Task 1.3: Pre-commit Configuration Create `.pre-commit-config.yaml`: ```yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-merge-conflict - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black language_version: python3.11 - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8 additional_dependencies: [flake8-docstrings] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.3.0 hooks: - id: mypy additional_dependencies: [types-all] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] ``` --- ## Phase 2: Application Development ### Purpose and Objectives This phase focuses on creating a well-structured Flask application that demonstrates modern Python development practices. The application will serve as the foundation for showcasing DevOps practices throughout the project. ### Application Architecture We'll build a RESTful API with the following features: - Health check endpoints - User management - Task management - Metrics exposure for monitoring - Comprehensive logging ### Industry Best Practices - **Separation of Concerns**: Clear separation between routes, business logic, and data models - **Configuration Management**: Environment-based configuration - **Error Handling**: Comprehensive error handling and logging - **API Design**: RESTful principles and OpenAPI documentation - **Testing**: Unit, integration, and end-to-end tests ### Step-by-Step Implementation #### Task 2.1: Application Structure Setup Create the main application files: `app/__init__.py`: ```python """Flask application factory pattern implementation.""" from flask import Flask from flask_cors import CORS from flask_limiter import Limiter from flask_limiter.util import get_remote_address import logging import os from typing import Optional def create_app(config_name: Optional[str] = None) -> Flask: """ Application factory pattern for creating Flask app instances. Args: config_name: Configuration environment name Returns: Configured Flask application instance """ app = Flask(__name__) # Load configuration config_name = config_name or os.getenv('FLASK_ENV', 'development') app.config.from_object(f'app.config.{config_name.title()}Config') # Initialize extensions CORS(app) # Rate limiting limiter = Limiter( app, key_func=get_remote_address, default_limits=["1000 per hour"] ) # Configure logging configure_logging(app) # Register blueprints from app.routes.health import health_bp from app.routes.api import api_bp app.register_blueprint(health_bp) app.register_blueprint(api_bp, url_prefix='/api/v1') return app def configure_logging(app: Flask) -> None: """Configure application logging.""" if not app.debug and not app.testing: if not os.path.exists('logs'): os.mkdir('logs') file_handler = logging.FileHandler('logs/app.log') file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('Flask DevOps application startup') ``` #### Task 2.2: Configuration Management Create `app/config.py`: ```python """Application configuration management.""" import os from typing import Type class Config: """Base configuration class.""" SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production' # Database configuration DATABASE_URL = os.environ.get('DATABASE_URL') or 'sqlite:///app.db' # Redis configuration for caching REDIS_URL = os.environ.get('REDIS_URL') or 'redis://localhost:6379/0' # Logging configuration LOG_LEVEL = os.environ.get('LOG_LEVEL') or 'INFO' # Metrics configuration METRICS_ENABLED = os.environ.get('METRICS_ENABLED', 'true').lower() == 'true' class DevelopmentConfig(Config): """Development environment configuration.""" DEBUG = True TESTING = False class TestingConfig(Config): """Testing environment configuration.""" DEBUG = False TESTING = True DATABASE_URL = 'sqlite:///:memory:' class StagingConfig(Config): """Staging environment configuration.""" DEBUG = False TESTING = False class ProductionConfig(Config): """Production environment configuration.""" DEBUG = False TESTING = False config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'staging': StagingConfig, 'production': ProductionConfig, 'default': DevelopmentConfig } def get_config(config_name: str) -> Type[Config]: """Get configuration class by name.""" return config.get(config_name, config['default']) ``` #### Task 2.3: Health Check Endpoints Create `app/routes/health.py`: ```python """Health check endpoints for monitoring and load balancing.""" from flask import Blueprint, jsonify, current_app from datetime import datetime import psutil import os health_bp = Blueprint('health', __name__) @health_bp.route('/health') def health_check(): """ Basic health check endpoint. Returns: JSON response indicating service health status """ return jsonify({ 'status': 'healthy', 'timestamp': datetime.utcnow().isoformat(), 'service': 'flask-devops-app', 'version': os.environ.get('APP_VERSION', '1.0.0') }) @health_bp.route('/health/ready') def readiness_check(): """ Kubernetes readiness probe endpoint. Checks if the application is ready to receive traffic. """ try: # Add checks for database connectivity, external services, etc. checks = { 'database': check_database_connection(), 'external_apis': check_external_dependencies() } all_ready = all(checks.values()) status_code = 200 if all_ready else 503 return jsonify({ 'status': 'ready' if all_ready else 'not_ready', 'checks': checks, 'timestamp': datetime.utcnow().isoformat() }), status_code except Exception as e: current_app.logger.error(f"Readiness check failed: {str(e)}") return jsonify({ 'status': 'not_ready', 'error': str(e), 'timestamp': datetime.utcnow().isoformat() }), 503 @health_bp.route('/health/live') def liveness_check(): """ Kubernetes liveness probe endpoint. Checks if the application is running and should be restarted if not. """ try: # Basic liveness checks memory_usage = psutil.virtual_memory().percent cpu_usage = psutil.cpu_percent(interval=1) # Consider unhealthy if memory usage > 90% or CPU > 95% is_healthy = memory_usage < 90 and cpu_usage < 95 return jsonify({ 'status': 'alive' if is_healthy else 'unhealthy', 'metrics': { 'memory_usage_percent': memory_usage, 'cpu_usage_percent': cpu_usage }, 'timestamp': datetime.utcnow().isoformat() }), 200 if is_healthy else 503 except Exception as e: current_app.logger.error(f"Liveness check failed: {str(e)}") return jsonify({ 'status': 'unhealthy', 'error': str(e), 'timestamp': datetime.utcnow().isoformat() }), 503 def check_database_connection() -> bool: """Check database connectivity.""" # Implement actual database connection check return True def check_external_dependencies() -> bool: """Check external service dependencies.""" # Implement checks for external APIs, services, etc. return True ``` --- ## Phase 3: Containerization with Docker ### Purpose and Objectives Containerization ensures consistent deployment across different environments and simplifies the deployment process. This phase focuses on creating optimized, secure Docker images following industry best practices. ### Docker Best Practices - **Multi-stage builds**: Reduce image size and improve security - **Minimal base images**: Use distroless or Alpine images - **Security scanning**: Implement vulnerability scanning - **Layer optimization**: Minimize layers and leverage caching - **Non-root execution**: Run containers as non-root users ### Step-by-Step Implementation #### Task 3.1: Production Dockerfile Create `docker/Dockerfile`: ```dockerfile # Multi-stage build for optimized production image FROM python:3.11-slim as builder # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ curl \ && rm -rf /var/lib/apt/lists/* # Create and activate virtual environment RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" # Copy requirements and install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Production stage FROM python:3.11-slim as production # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH="/opt/venv/bin:$PATH" \ FLASK_ENV=production # Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean # Create non-root user RUN groupadd -r appuser && useradd -r -g appuser appuser # Copy virtual environment from builder stage COPY --from=builder /opt/venv /opt/venv # Set working directory WORKDIR /app # Copy application code COPY app/ ./app/ COPY wsgi.py ./ # Create necessary directories and set permissions RUN mkdir -p /app/logs && \ chown -R appuser:appuser /app # Switch to non-root user USER appuser # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # Expose port EXPOSE 8000 # Use Gunicorn for production CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "120", "wsgi:app"] ``` #### Task 3.2: Development Dockerfile Create `docker/Dockerfile.dev`: ```dockerfile # Development Dockerfile with hot reloading FROM python:3.11-slim # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ FLASK_ENV=development \ FLASK_DEBUG=1 # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ curl \ git \ && rm -rf /var/lib/apt/lists/* # Set working directory WORKDIR /app # Copy requirements and install dependencies COPY requirements-dev.txt . RUN pip install --no-cache-dir -r requirements-dev.txt # Copy application code (will be overridden by volume in docker-compose) COPY . . # Expose port EXPOSE 5000 # Use Flask development server CMD ["flask", "run", "--host=0.0.0.0", "--port=5000", "--reload"] ``` #### Task 3.3: Docker Compose Configuration Create `docker/docker-compose.yml`: ```yaml version: '3.8' services: app: build: context: .. dockerfile: docker/Dockerfile.dev ports: - "5000:5000" volumes: - ../app:/app/app - ../tests:/app/tests environment: - FLASK_ENV=development - FLASK_DEBUG=1 - DATABASE_URL=sqlite:///app.db - REDIS_URL=redis://redis:6379/0 depends_on: - redis networks: - app-network redis: image: redis:7-alpine ports: - "6379:6379" networks: - app-network prometheus: image: prom/prometheus:latest ports: - "9090:9090" volumes: - ../monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml networks: - app-network grafana: image: grafana/grafana:latest ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-storage:/var/lib/grafana networks: - app-network networks: app-network: driver: bridge volumes: grafana-storage: ``` #### Task 3.4: Docker Ignore File Create `.dockerignore`: ``` # Git .git .gitignore # CI/CD .github/ # Python __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # Virtual environments venv/ env/ ENV/ # IDE .vscode/ .idea/ *.swp *.swo *~ # OS .DS_Store Thumbs.db # Logs *.log logs/ # Environment files .env* !.env.example # Testing .coverage .pytest_cache/ htmlcov/ # Documentation docs/_build/ # Terraform *.tfstate *.tfstate.* .terraform/ # Kubernetes *.tmp ``` --- ## Phase 4: Version Control and Git Workflow ### Purpose and Objectives Implementing a robust version control strategy is essential for managing code changes, enabling collaboration, and maintaining release quality. This phase establishes Git workflows that align with industry standards. ### Git Workflow Strategy We'll implement Git Flow, which provides a robust branching model suitable for projects with scheduled releases and multiple environments. ### Branching Strategy - **main**: Production-ready code - **develop**: Integration branch for features - **feature/***: Individual feature development - **release/***: Release preparation - **hotfix/***: Emergency fixes to production ### Versioning Strategy We'll use Semantic Versioning (SemVer) with the format MAJOR.MINOR.PATCH: - **MAJOR**: Breaking changes - **MINOR**: New features (backward compatible) - **PATCH**: Bug fixes (backward compatible) ### Step-by-Step Implementation #### Task 4.1: Git Configuration Create `.gitignore`: ``` # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # IDE .vscode/ .idea/ *.swp *.swo *~ # OS .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # Terraform *.tfstate *.tfstate.* .terraform/ .terraform.lock.hcl # Kubernetes *.tmp # Logs logs/ *.log # Environment files .env.* !.env.example ``` #### Task 4.2: Conventional Commits Configuration Create `.gitmessage`: ``` # [optional scope]: # # [optional body] # # [optional footer(s)] # # Type must be one of the following: # feat: A new feature # fix: A bug fix # docs: Documentation only changes # style: Changes that do not affect the meaning of the code # refactor: A code change that neither fixes a bug nor adds a feature # perf: A code change that improves performance # test: Adding missing tests or correcting existing tests # build: Changes that affect the build system or external dependencies # ci: Changes to our CI configuration files and scripts # chore: Other changes that don't modify src or test files # revert: Reverts a previous commit # # Examples: # feat(auth): add OAuth2 integration # fix(api): resolve timeout issue in user endpoint # docs: update deployment guide # style: fix code formatting # refactor(database): optimize query performance # test(integration): add user registration tests # ci: update GitHub Actions workflow ``` #### Task 4.3: Git Hooks Setup Create `scripts/git-hooks/commit-msg`: ```bash #!/bin/bash # Conventional commits validation commit_regex='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,50}' if ! grep -qE "$commit_regex" "$1"; then echo "Invalid commit message format!" echo "" echo "Valid format: [optional scope]: " echo "" echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert" echo "" echo "Examples:" echo " feat(auth): add OAuth2 integration" echo " fix(api): resolve timeout issue" echo " docs: update deployment guide" echo "" exit 1 fi ``` #### Task 4.4: Release Management Script Create `scripts/release.sh`: ```bash #!/bin/bash # Automated release script following semantic versioning set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Functions log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Get current version from git tags get_current_version() { git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0" } # Parse version components parse_version() { local version=$1 version=${version#v} # Remove 'v' prefix echo $version | sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)/\1 \2 \3/' } # Increment version based on type increment_version() { local current_version=$1 local increment_type=$2 read -r major minor patch <<< $(parse_version $current_version) case $increment_type in major) major=$((major + 1)) minor=0 patch=0 ;; minor) minor=$((minor + 1)) patch=0 ;; patch) patch=$((patch + 1)) ;; *) log_error "Invalid increment type: $increment_type" exit 1 ;; esac echo "v${major}.${minor}.${patch}" } # Main release function create_release() { local increment_type=$1 # Validate git status if [[ -n $(git status --porcelain) ]]; then log_error "Working directory is not clean. Please commit or stash changes." exit 1 fi # Ensure we're on develop branch current_branch=$(git rev-parse --abbrev-ref HEAD) if [[ "$current_branch" != "develop" ]]; then log_error "Releases must be created from the develop branch" exit 1 fi # Get current version and calculate new version current_version=$(get_current_version) new_version=$(increment_version $current_version $increment_type) log_info "Current version: $current_version" log_info "New version: $new_version" # Create release branch release_branch="release/$new_version" log_info "Creating release branch: $release_branch" git checkout -b $release_branch # Update version in files echo $new_version > VERSION sed -i.bak "s/VERSION = .*/VERSION = \"${new_version#v}\"/" app/__init__.py rm -f app/__init__.py.bak