Post

Building a Secure CI/CD Pipeline

Building a Secure CI/CD Pipeline

Building a Secure CI/CD Pipeline for a Python App: From Code to Render Deployment

Modern web development isn’t just about building cool apps, it’s about building secure, automated, and production-ready systems. In this post, we walk through how I built, secured, and deployed a Python-based web application with an end-to-end CI/CD pipeline using GitLab, Render, and industry-standard security tools like Semgrep, Bandit, Trivy, and Snyk.

Whether you’re a DevOps enthusiast or a security conscious developer, this guide will show you how to shift security left, streamline deployment, and automate reporting, all in one place


Overview of the Stack

App: Python web app (Flask-based)

CI/CD: GitLab Pipelines

Hosting: Render

Security Tools:

  • Semgrep for static code analysis

  • Bandit for Python-specific SAST

  • Snyk for dependency scanning

  • Trivy for filesystem + container scanning

Alerting: Telegram integration with summarized report

Step 1: Building the App

I started with a lightweight Flask app, containing routes, business logic, and templates. The key focus at this stage was to write minimal but functional code to serve as the base for our secure deployment.

Sample structure:

1
2
3
4
5
fauxmart/
├── app.py
├── requirements.txt
├── templates/
└── Dockerfile

The requirements.txt contained typical dependencies like Flask, while the Dockerfile defined a production-ready image using a minimal base (e.g., python:3.10-slim). Link to the demo-app can be found

Step 2: Creating the CI/CD Pipeline in GitLab

Next, I created a robust .gitlab-ci.yml that followed a secure-first, build-later approach using three main stages:

CI/CD Pipeline Stages:

1
2
3
4
- security_audit   # Perform all security scan
- report           # Generate and send summary
- build            # Build & push Docker image
- deploy           # Trigger Render deployment

I’m using gitlab here because I need to ght more familiar with it. Already used to Github action, in which I once built a mini pipeline security on before.(Vulnera).

Step 3: Integrating Security Tools

Security is not just some checkbox, it’s a continuous process. I embedded 4 security tools directly into the pipeline:

  1. Semgrep - Static Code Scanning

    1
    2
    3
    4
    
     semgrep_scan:
     image: returntocorp/semgrep:latest
     script:
         - semgrep --config .semgrep.yml . --json --metrics=off > semgrep_results.json
    

    Semgrep detects hardcoded secrets, missing CSRF tokens, and exposed admin routes, among others.

  2. Bandit - Python-Specific SAST ```yaml bandit_scan: image: python:3.10 script:
    • pip install bandit
    • bandit -r . -f json -o bandit_results.json

    ``` Bandit catches Python-specific issues like insecure hash functions, wildcard imports, and more

  3. Snyk - Dependency Scanning
    1
    2
    3
    4
    5
    
     snyk_scan:
     image: snyk/snyk-cli:docker
     script:
         - snyk auth "$SNYK_TOKEN"
         - snyk test --file=requirements.txt --json > snyk_results.json
    

    Unlike image-only scans, I used Snyk to deeply inspect requirements.txt and detect vulnerable libraries. Also, integrated Synk by importing the whole codebase and monitoring from the GUI, to avoid any slip up.

  4. Trivy - Filesystem + Secret + Config Scanner
    1
    2
    3
    4
    
     trivy_scan:
     image: aquasec/trivy:latest
     script:
         - trivy fs . --scanners config,secret,vuln --format json -o trivy_results.json
    

    Trivy catches exposed secrets, insecure configuration files, and vulnerabilities in the file system before building the image.

Step 4: Generating a Security Report

After all scans are complete, a custom Python script, summary.py collates the findings into a security_report.txt. This report is automatically sent to a Telegram channel I created, using a bot token.

alt

alt

This empowers us with real-time feedback. No need to log in and check dashboards.

Step 5: Docker Image Build and Push

Once all scans complete and the report is sent, the pipeline proceeds to build and push the Docker image to Docker Hub:

1
2
3
4
5
6
7
build_and_push:
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE

This ensures only post-verified code goes into production containers.

Step 6: Automated Deployment to Render

I deployed using Render’s API via a simple cURL command:

1
2
3
4
5
6
7
trigger_render_deploy:
  image: curlimages/curl:latest
  script:
    - curl -X POST \
        -H "Authorization: Bearer $RENDER_API_KEY" \
        -d '{}' \
        https://api.render.com/v1/services/$RENDER_SERVICE_ID/deploys

alt

alt

alt

This step automatically redeploys the latest secure image to the production environment.

Notification Integration: Instant Security Alerts

All findings are summarized and posted to a dedicated Telegram group/bot for instant visibility. This is especially useful for small teams and solo engineers who want fast feedback without heavy dashboard tools.

For large teams, Slack or Email can be used instead.

Conclusion

This workflow offers a zero-click security-first CI/CD pipeline that:

  • Analyzes code and dependencies

  • Scans for secrets, misconfigurations, and vulnerable packages

  • Prevents insecure deployments

  • Sends automated reports to your team

Tools Mentioned

ToolPurposeLink
SemgrepStatic Application Securityhttps://semgrep.dev
BanditPython Code Analysishttps://bandit.readthedocs.io
SnykDependency Vulnerabilityhttps://snyk.io
TrivyFile/Image Vulnerability Scanhttps://aquasecurity.github.io/trivy
RenderDeployment Platformhttps://render.com
GitLabCI/CD Automationhttps://gitlab.com

Security isn’t an afterthought. it’s built-in


Samuel (iPsalmy)
GitHubLinkedInTwitter/X

This post is licensed under CC BY 4.0 by the author.