DEV Community

Cover image for A Robust CI/CD Pipeline: Dev to Prod
Wisaroot Lertthaweedech
Wisaroot Lertthaweedech

Posted on Originally published at wisl.dev

A Robust CI/CD Pipeline: Dev to Prod

Originally published on wisl.dev.

In modern software development, CI/CD is more than a luxury: it's a necessity. It ensures consistent quality, security, and speed. Over the past year, I led the initiative to implement a full CI/CD pipeline in our team. We focused on enforcing best practices, automating everything we could, and ensuring developer experience wasn't sacrificed along the way.

Let me walk you through the components of our CI/CD pipeline: how each stage fits into the development lifecycle, the tools we chose (and why), and some hard-earned lessons we picked up along the way.

CI/CD pipeline flowchart: pull request gates, pre-release versioning, E2E in staging, semantic release, deploy to prod, scheduled re-scans

CI/CD Platform: We use GitHub Actions to orchestrate our CI/CD workflows. Its tight integration with GitHub, support for reusable workflows, and built-in secret management made it a natural choice for our team.

CI: Continuous Integration

CI runs every time a pull request (PR) is opened. It's our safety net, catching problems early before they hit production.

1. Secret Scanning with Gitleaks or GitGuardian

First, we run Gitleaks and GitGuardian to scan for secrets accidentally committed to the repo: API keys, tokens, credentials, etc. This has saved us multiple times. Mistakes happen, but automation makes sure they don't make it to main.

A leaked credential in a public repo gets scraped by bots within minutes, long before a human reviewer would notice it. Secret scanning is the one CI stage that should block a merge outright, every time.

2. Security & License Scanning with Trivy

We use Trivy to scan for vulnerabilities in:

  • Our dependencies (including transitive ones),
  • Docker images,
  • IaC misconfigurations,
  • And even open-source licenses (to avoid legal issues).

It's comprehensive, fast, and runs directly in our CI.

3. pre-commit Checks

Even though we require developers to run pre-commit locally, CI acts as the last line of defense. We rerun it to catch cases where it might not have been installed or configured properly. Our hooks include:

  • Code formatting,
  • Trailing whitespace cleanup,
  • Linting,
  • And other team-specific checks.

4. Unit Tests

We run unit tests using pytest, verifying each function does what it's supposed to. Fast feedback here ensures contributors don't break small pieces of logic.

5. Integration Tests

Integration tests combine multiple components together: database + business logic, service A + service B, etc. We run these in isolated environments to simulate real-world conditions.

6. Pre-Release Versioning

Before merging to main, we generate a pre-release version of the service or package. This helps us test in staging environments without affecting the production release stream.

We follow a structured format for the version:

1.2.3-rc.4.dev.5+branch.name.timestamp.commit.hash
Enter fullscreen mode Exit fullscreen mode

This includes:

  • The base version it's targeting (e.g., 1.2.3)
  • rc (release candidate) or dev indicator
  • A unique suffix with the branch name, timestamp, and commit hash for traceability

These versions are automatically bumped and tagged in CI, then deployed to non-prod environments or published to a test PyPI index (like an internal Google Artifact Registry). This allows QA and downstream services to test changes before the official semantic release happens in CD.

7. E2E & Pre-Release Testing

This is where things get real.

We deploy the service to a dedicated non-production environment that connects to actual downstream and upstream services. It's the closest simulation to production, giving us confidence that everything works end to end: APIs, data flow, infrastructure, and third-party integrations.

For Python libraries or internal packages, we generate and publish pre-release versions.

These pre-releases are deployed or installed in staging environments for end-to-end testing, without polluting the main release channel.

It's our last gate before merging to main and triggering the real release.

8. Code Quality with SonarCloud

We analyze every PR with SonarCloud, which gives insights into:

  • Maintainability,
  • Security hotspots,
  • Code duplication,
  • Reliability,
  • And test coverage.

It's a great way to spot tech debt before it grows.

9. Semantic Pull Requests

We enforce Semantic PR titles like:

feat: add new user registration API
fix: resolve login timeout issue
Enter fullscreen mode Exit fullscreen mode

This enables semantic release (more on that below) to automate versioning and changelog generation.

CD: Continuous Deployment

CD kicks in after a PR is merged into the main branch. This is where the code goes live.

1. Semantic Release

We use semantic release to automatically bump version numbers based on the PR title:

  • feat: → minor bump
  • fix: or others → patch bump
  • feat!: or BREAKING CHANGE: in footer → major bump

This removes the need to manually edit version files or changelogs.

Once released, the service or package gets a clean, production-ready version like 1.2.3. This version is free of any -rc, .dev, or build metadata, ideal for production deployments or publishing to a package registry.

2. Production & Non-Production Deployments

Once versioned, we:

  • Deploy the service to production and non-production environments.
  • Or publish the package with a clean version tag like 1.2.3.

Everything is traceable and reproducible.

3. Gitleaks & Trivy (Again)

We rerun Gitleaks and Trivy post-merge to double-check for secrets or new vulnerabilities. It's a belt-and-suspenders approach, and it's worth it.

Continuous Reruns & Dependency Updates

Security isn't a one-time thing. Even "safe" dependencies can become vulnerable tomorrow.

  • Trivy's database updates daily, so we schedule it to rerun periodically to catch new issues in our existing dependencies.
  • Tools like Renovate or Dependabot help us auto-update stale dependencies. Older packages are prime targets for exploits, so we keep our stack fresh.

Final Thoughts

CI/CD is never truly "done." It evolves with your team and your product. But the foundation we've built (security-first, automated, and feedback-rich) gives us confidence with every change we ship.

If you're just getting started, start small. Automate one thing at a time. But don't wait too long: your future self (and your team) will thank you.

If you adopt only three stages, make them secret scanning, unit tests, and semantic PR titles. That trio alone buys you most of the safety, and the semantic titles unlock automated releases later without reworking anything.

Let me know how your CI/CD journey is going. I'd love to hear what's worked for you.

Top comments (1)

Collapse
 
raknaos profile image
Raknaos

Secret scanning as the one stage that blocks a merge outright is the right prioritization — everything else in a pipeline can be a warning with a follow-up ticket, but a scraped credential has a minutes-long half-life, so 'review it later' isn't a real option for that one.

The pre-release versioning format with branch, timestamp and commit hash is more traceability than most teams bother with, and pairing it with a test package index keeps staging installs from polluting the real release channel. The trio you'd keep (secret scanning, unit tests, semantic PR titles) matches what I've seen survive contact with actual teams — did you find the semantic-title enforcement needed a grace period, or did contributors adapt to feat:/fix: without friction?