Developer DevOps & CI/CD Guide#
Table of Contents#
- 1. Shift-Left Philosophy: Dev vs SRE/Platform
- 2. CI/CD Workflows: GitHub Actions & Automations
- 3. The 12-Factor App in Practice
- 4. Developer Observability: Logs, Traces & Metrics
- 5. Deployment Strategies: Rolling, Blue/Green & Canary
- 6. Local-to-Production Parity with Docker Compose
1. Shift-Left Philosophy: Dev vs SRE/Platform#
Traditionally, developers wrote software and "threw it over the wall" to operations teams to run. DevOps for Developers is about owning your code from the moment you write your first unit test to when it runs in production, without having to configure bare-metal networks or cloud subnets.
The Developer's Scope
- Continuous Integration: Automated test suites, linting, security audits on every pull request.
- Containerized Packaging: Creating fast, reproducible, and minimal container images with multi-stage Docker builds.
- Environment Parity: Running identical dependencies locally in Docker Compose as in cloud staging/production.
- Application Observability: Emitting structured JSON logs, trace IDs, and business/runtime metrics to diagnose bugs in minutes instead of hours.
2. CI/CD Workflows: GitHub Actions & Automations#
Continuous Integration (CI) guarantees that code committed to a repository builds cleanly and passes all tests before merging. Continuous Delivery/Deployment (CD) automatically packages and deploys merged code to staging or production.
Below is a production-grade GitHub Actions workflow that runs linting, tests, builds a multi-stage Docker image, and publishes it to a container registry:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install Dependencies
run: |
pip install -r requirements.txt
pip install pytest ruff
- name: Lint Code
run: ruff check .
- name: Run Unit Tests
run: pytest tests/ -v
build-and-push:
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
3. The 12-Factor App in Practice#
The 12-Factor App methodology is the blueprint for modern, cloud-native applications. Here are the core factors developers interact with daily:
- Codebase: One codebase tracked in revision control, many deploys (dev, staging, prod).
- Explicit Dependencies: Explicitly declare and isolate dependencies (e.g.
package-lock.json,poetry.lock,requirements.txt). Never assume system-wide libraries. - Config in Environment: Store configuration that varies between deploys (database credentials, API endpoints) in environment variables, never hardcoded in source code.
- Backing Services as Attached Resources: Treat databases, message queues, and SMTP servers as attached resources accessed via URLs/credentials.
- Stateless Processes: Execute the app as one or more stateless processes. Never store session state on the local filesystem (use Redis or database). Any container can be destroyed or recreated at any moment without data loss.
- Port Binding: The app is completely self-contained and exports HTTP/gRPC by binding to a
port (e.g.
app.listen(8080)). - Disposability: Maximize robustness with fast startup and graceful shutdown upon receiving
SIGTERM. - Dev/Prod Parity: Keep development, staging, and production as similar as possible using containerization.
4. Developer Observability: Logs, Traces & Metrics#
Platform engineers care about server CPU and disk space. Developers care about application observability: understanding why a request failed or took 4 seconds.
Structured JSON Logging
Never emit unformatted plain text strings like print("User logged in"). Modern log aggregation
engines (Datadog, Loki, CloudWatch) parse structured JSON:
{
"timestamp": "2026-09-08T06:15:00.123Z",
"level": "INFO",
"message": "Processed payment transaction",
"requestId": "req-98f2-4bc1",
"userId": "usr-10492",
"durationMs": 42.8,
"service": "payment-api"
}
Distributed Tracing & Trace Context
In microservices or multi-step agent pipelines, a single user click might touch 4 services. With
OpenTelemetry and W3C trace context, a unique traceparent header is forwarded
along every HTTP request, allowing you to see an end-to-end flame graph of execution:
# W3C Traceparent Header Format:
# version - trace_id (32 hex) - parent_span_id (16 hex) - trace_flags
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
5. Deployment Strategies: Rolling, Blue/Green & Canary#
How your application updates in production directly affects code design, database migrations, and backward compatibility:
1. Rolling Update
New pods/containers are started gradually while old ones are terminated. Developer requirement: Version N and Version N+1 of your code will run concurrently for a few minutes. All database schema migrations must be backward-compatible (e.g. never drop a database column in the same release you stop using it).
2. Blue / Green Deployment
Two identical production environments exist. Blue runs live traffic (v1). Green is deployed with v2 and verified with smoke tests. Once verified, the router/load balancer instantly flips 100% of traffic from Blue to Green. Rollback is an instantaneous flip back.
3. Canary Release
A small percentage of real user traffic (e.g. 5%) is routed to the new version (the "canary"). If error rates or latency spike, the canary is rolled back automatically before impacting all users.
6. Local-to-Production Parity with Docker Compose#
The "it works on my machine" problem is solved with Docker Compose. A developer can spin up
their application, database, Redis cache, and mock APIs with one command: docker compose up.
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app # Hot-reloading: changes in code reflect immediately!
- /app/node_modules # Prevent local host modules from overriding container
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://dev:secret@postgres:5432/appdb
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d appdb"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata: