Back to Blog
DevOpsCI/CDDevOpsAutomationGitHub Actions

CI/CD Implementation Guide: From Setup to Production

December 20, 2023
10 min read
DT

DevOps Team

Infrastructure & Automation

CI/CD Implementation Guide: From Setup to Production

40-60% Cost Savings

Compared to Western European or North American developers

90%+ Satisfaction

Client satisfaction rates with Eastern European developers

6-8 Hours Overlap

Perfect time zone alignment with US East Coast

$2.3B Investment

Venture capital funding in Eastern Europe (2023)

CI/CD Implementation Guide: From Setup to Production

Three years ago, our deployment process was a nightmare. Every Friday at 5 PM, someone (usually me) would SSH into a production server, run a series of manual commands, hold their breath, and hope nothing broke.

Spoiler alert: Things broke. A lot.

One particularly memorable Friday, a typo in a database migration brought down our entire platform for 4 hours. Our CEO was on a sales call when it happened. That Monday, he walked into my office and said: "Fix this. I don't care how."

That's when I discovered CI/CD. Not from a blog post or conference talk, but from desperation and 4 hours of weekend reading.

Today, we deploy 30+ times per day with zero manual intervention. Our downtime went from 12 hours/month to less than 5 minutes/month. And I never work Fridays at 5 PM anymore.

Here's everything I learned the hard way about implementing CI/CD, so you don't have to.

The Breaking Point: Why We Needed CI/CD

Let me paint you a picture of our old deployment process:

The "Deploy Checklist" (38 steps, no joke):

  1. Pull latest code on local machine
  2. Run tests manually
  3. Check if tests pass (they didn't always)
  4. Build production bundle
  5. SSH into staging server
  6. Upload files via SCP
  7. Run database migrations
  8. Restart application server
  9. Check staging site manually
  10. ... 28 more steps ...

Average deployment time: 45 minutes

Success rate on first try: Maybe 60%

Number of times I forgot a step: Lost count after 20

1. The Real Costs of Manual Deployment

Here's what our manual process was actually costing us:

MetricBefore CI/CDHidden Cost
Deployment Time45 min average15 hours/month of developer time
Failed Deployments40%Stress, rollback time, customer complaints
Deployment Frequency3-5 per weekDelayed features, batched changes
Hotfix Deployment2+ hoursRevenue loss during outages
Weekend DeploymentsMonthly "necessity"Team burnout, poor work-life balance

The wake-up call: We calculated that manual deployments cost us $12,000/month in developer time alone. Not counting the cost of downtime, bugs reaching production, or developer sanity.

2. The Incident That Changed Everything

Remember that Friday database migration disaster? Here's what actually happened:

Example
# What I meant to run
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;

# What I actually ran (missing semicolon caused next command to concatenate)
ALTER TABLE users ADD COLUMN last_login TIMESTAMP
DROP TABLE sessions;

Result: All user sessions deleted. Everyone logged out. 4 hours to restore from backup.

Root cause: Manual typing in production terminal. No review. No testing. No rollback plan.

That's when I started researching CI/CD.

Choosing Your CI/CD Tools: The Decision Framework

I spent 2 weeks testing every CI/CD platform I could find. Here's what I learned:

Tool Comparison

ToolBest ForProsConsCost (for our team)
GitHub ActionsGitHub-hosted projectsNative integration, free tierFewer third-party integrations$0 (public repos)
GitLab CIGitLab usersBuilt-in, powerful featuresLearning curve$0 (included)
CircleCIDocker-heavy workflowsFast, great Docker supportExpensive at scale$150/month
JenkinsFull control neededFree, infinitely customizableMaintenance nightmare$0 (self-hosted)
Travis CIOpen source projectsSimple configSlower builds$69/month

We chose GitHub Actions because:

  • Our code was already on GitHub
  • Free for private repos (up to 2,000 minutes/month)
  • YAML configuration was straightforward
  • Marketplace had everything we needed
  • No infrastructure to maintain

1. Start Simple: Your First Pipeline

Don't try to build the perfect CI/CD pipeline on day one. Start with the bare minimum.

Our first GitHub Actions workflow was literally 15 lines:

Example
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install
      - name: Run tests
        run: npm test

That's it. No deployment. No fancy caching. Just automated tests on every push.

Impact: We caught 3 bugs in the first week that would have reached production.

2. Add Continuous Integration First

Before thinking about deployment, nail your CI. Here's how we evolved our pipeline:

Week 1: Basic Testing

  • Run unit tests
  • Check if code compiles

Week 2: Code Quality

  • Linting (ESLint)
  • Code formatting checks (Prettier)
  • Type checking (TypeScript)

Week 3: Advanced Testing

  • Integration tests
  • Test coverage reporting
  • Parallel test execution

Week 4: Performance

  • Build caching
  • Docker layer caching
  • Dependency caching

Result after 1 month: Our CI pipeline went from 8 minutes to 2.5 minutes. Every PR got automated feedback within 3 minutes.

3. Deployment Strategy: Choose Your Adventure

Once CI was solid, we tackled CD (Continuous Deployment). We tried multiple strategies:

Blue-Green Deployment (Our Choice)

Run two identical production environments. Deploy to inactive one, swap traffic.

Pros:

  • Zero-downtime deployments
  • Instant rollback (just swap back)
  • Test in production-identical environment

Cons:

  • Doubles infrastructure cost
  • Database migrations tricky
  • Requires load balancer

Canary Deployment

Deploy to small subset of servers/users first, gradually roll out.

Pros:

  • Minimal risk
  • Real user testing
  • Easy to abort

Cons:

  • Complex traffic routing
  • Longer deployment time
  • Monitoring overhead

Rolling Deployment

Update servers one at a time while others handle traffic.

Pros:

  • No extra infrastructure
  • Gradual rollout
  • Simple implementation

Cons:

  • Mixed versions in production
  • Longer deployment window
  • Complex rollbacks

We use Blue-Green for our main app and Rolling for microservices.

Building the Pipeline: Step by Step

Here's our production GitHub Actions workflow, explained section by section:

1. Trigger Configuration

Example
name: Production Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

What it does: Runs on every push to main and every PR targeting main.

Why this matters: PRs get tested before merge. Main branch deploys automatically.

2. Environment Setup

Example
jobs:
  test-and-deploy:
    runs-on: ubuntu-latest

    env:
      NODE_ENV: production
      DATABASE_URL: ${{ secrets.DATABASE_URL }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

Key points:

  • Secrets stored in GitHub Settings, never in code
  • Node version pinned (no surprises from version changes)
  • NPM cache enabled (saves 30 seconds per build)

3. Install and Test

Example
- name: Install dependencies
  run: npm ci

- name: Run linting
  run: npm run lint

- name: Run tests
  run: npm run test:ci

- name: Generate coverage
  run: npm run test:coverage

- name: Upload coverage
  uses: codecov/codecov-action@v3

Why npm ci instead of npm install?

  • Faster (no package.json resolution)
  • Deterministic (installs exact versions from package-lock.json)
  • Fails if lock file is out of sync

4. Build Application

Example
- name: Build application
  run: npm run build

- name: Run build size check
  run: |
    SIZE=$(du -sh dist | cut -f1)
    echo "Build size: $SIZE"
    # Fail if over 10MB
    if [ $(du -s dist | cut -f1) -gt 10240 ]; then
      echo "Build too large!"
      exit 1
    fi

Smart checks we added:

  • Build size monitoring
  • Bundle analysis
  • Dead code detection
  • Asset optimization verification

5. Deploy to Production

Example
- name: Deploy to production
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  run: |
    # Deploy using your preferred method
    # Examples: AWS, Vercel, custom script
    ./scripts/deploy.sh

- name: Run smoke tests
  run: npm run test:smoke

- name: Notify team
  if: always()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    text: 'Deployment ${{ job.status }}'
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Critical components:

  • Only deploys on main branch pushes (not PRs)
  • Runs smoke tests after deployment
  • Always notifies team (success or failure)

The Testing Strategy That Actually Works

We learned this the hard way: not all tests belong in CI.

Test Pyramid (Our Implementation)

Test TypeCountExecution TimeWhen to Run
Unit Tests85030 secondsEvery commit
Integration Tests1202 minutesEvery commit
E2E Tests258 minutesBefore deployment
Performance Tests1015 minutesNightly
Security Scans-5 minutesEvery commit

1. Fast Feedback Loop

The rule: CI pipeline must complete in under 5 minutes for PRs.

How we achieved it:

  • Parallel test execution (4 workers)
  • Selective test running (only affected tests)
  • Fail-fast strategy (stop on first failure)
  • Cached dependencies and build artifacts

2. Test Quality Over Quantity

We used to have 1,200 tests. Half were garbage.

Bad tests we removed:

  • Flaky tests (failed randomly)
  • Slow tests (>5 seconds each)
  • Redundant tests (testing same thing 3 different ways)
  • Tests that tested implementation, not behavior

Result: Down to 995 tests. CI time dropped 40%. Confidence increased.

3. Handling Flaky Tests

Flaky tests will kill your CI/CD adoption. Here's how we handled them:

Our policy:

  1. First flaky failure: Create issue
  2. Second flaky failure: Quarantine test (run separately)
  3. Third flaky failure: Delete test or fix immediately

No exceptions. A flaky test that blocks deployments is worse than no test.

Environment Management: The Right Way

Managing multiple environments was our biggest challenge.

Our Environment Strategy

Example
Development → Staging → Production
     ↓           ↓            ↓
Auto-deploy  Auto-deploy  Manual approval

1. Environment Parity

Goal: Make staging identical to production.

What we did:

  • Same infrastructure (via Infrastructure as Code)
  • Same environment variables (different values, same keys)
  • Same deployment process
  • Same monitoring and logging

What broke parity:

  • Database size (staging has subset of prod data)
  • Third-party integrations (use sandbox APIs)
  • Traffic volume (can't replicate that)

2. Secrets Management

Never, ever, EVER commit secrets.

Our secrets hierarchy:

Example
GitHub Secrets (encrypted at rest)
     ↓
CI/CD Pipeline (uses secrets as env vars)
     ↓
Deployment Script (passes to infrastructure)
     ↓
Application (reads from environment)

Tools we use:

  • GitHub Secrets for CI/CD variables
  • AWS Secrets Manager for application secrets
  • .env.example (template with no actual values)
  • Git hooks to prevent secret commits

3. Configuration Management

Different configs for different environments:

Example
# config/production.js
module.exports = {
  database: {
    host: process.env.DB_HOST,
    pool: { min: 10, max: 50 }
  },
  cache: {
    enabled: true,
    ttl: 3600
  },
  logging: {
    level: 'error'
  }
}

# config/development.js
module.exports = {
  database: {
    host: 'localhost',
    pool: { min: 2, max: 10 }
  },
  cache: {
    enabled: false
  },
  logging: {
    level: 'debug'
  }
}

Key insight: Prod should be optimized for performance. Dev should be optimized for debugging.

Deployment Strategies in Practice

Theory is nice. Here's what actually works in production.

1. Blue-Green Deployment Implementation

Our blue-green setup using AWS:

Example
[Load Balancer]
     ↓
┌────────────┬────────────┐
│  Blue      │  Green     │
│  (Active)  │  (Idle)    │
└────────────┴────────────┘

Deployment process:

  1. Deploy new version to Green environment
  2. Run smoke tests against Green
  3. Gradually shift traffic (10%, 25%, 50%, 100%)
  4. Monitor error rates at each step
  5. If errors spike, instant rollback to Blue
  6. If all good, Green becomes active, Blue becomes idle

Actual deployment script snippet:

Example
# Deploy to green environment
./deploy-to-green.sh

# Health check
if ! curl -f https://green.myapp.com/health; then
  echo "Green deployment failed health check"
  exit 1
fi

# Shift 10% traffic
aws elbv2 modify-target-group-weights --target-group-weight blue=90,green=10

# Wait and monitor
sleep 300
check_error_rates

# Continue if no errors...

2. Database Migration Strategy

The problem: Blue and Green both hit the same database. How do you handle schema changes?

Our solution: Backward-compatible migrations

Phase 1: Add new column (backward compatible)

Example
ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT false;

Deploy new code. Old code ignores new column. New code uses it.

Phase 2: Migrate data (background job)

Example
UPDATE users SET email_verified = true WHERE email_confirmed_at IS NOT NULL;

Phase 3: Remove old column (next deployment)

Example
ALTER TABLE users DROP COLUMN email_confirmed_at;

Rule: Every migration must work with both old and new code for at least one deployment cycle.

3. Rollback Strategy

The harsh truth: You will need to rollback. Plan for it.

Our rollback options:

MethodSpeedData Loss RiskWhen to Use
Load balancer swap30 secondsNoneCode bugs, performance issues
Git revert + redeploy5 minutesNoneBad commit, feature issues
Database restore30+ minutesHighData corruption
Full infrastructure rollback2+ hoursModerateInfrastructure issues

Rollback checklist we actually use:

Example
[ ] Identify issue
[ ] Alert team (#incidents Slack channel)
[ ] Stop auto-deployment
[ ] Execute rollback
[ ] Verify rollback successful
[ ] Investigate root cause
[ ] Post-mortem within 24 hours

Monitoring and Observability

If you can't see it, you can't fix it.

1. Deployment Monitoring

Metrics we track per deployment:

Example
Deployment started: 2024-01-15 14:32:18 UTC
Deployment completed: 2024-01-15 14:37:42 UTC
Duration: 5m 24s

Health checks: ✓ Passed
Error rate: 0.02% (within threshold)
Response time p95: 245ms (baseline: 230ms)
Traffic: 1,245 req/min

Status: SUCCESS

Automated alerts:

  • Error rate >1% for 2 minutes
  • Response time >500ms p95
  • Health check failures
  • Deployment duration >10 minutes

2. Key Metrics We Actually Watch

MetricPre-CI/CDPost-CI/CDChange
Deployment Frequency3-5/week30+/day+1,400%
Lead Time2-3 days2-4 hours-90%
Mean Time to Recovery4 hours15 minutes-94%
Change Failure Rate40%8%-80%
Deployment Duration45 minutes5 minutes-89%

3. The Dashboard That Matters

We built a simple deployment dashboard:

Example
┌─────────────────────────────────────┐
│ Deployments Today: 23               │
│ Success Rate: 96%                   │
│ Average Duration: 4m 32s            │
│                                     │
│ Last 5 Deployments:                 │
│ ✓ v2.4.5  14:37  5m 24s  SUCCESS   │
│ ✓ v2.4.4  12:15  4m 12s  SUCCESS   │
│ ✗ v2.4.3  11:03  2m 35s  FAILED    │
│ ✓ v2.4.2  10:45  4m 58s  SUCCESS   │
│ ✓ v2.4.1  09:22  5m 02s  SUCCESS   │
└─────────────────────────────────────┘

Displayed on TV in office. Transparency drives quality.

Common Pitfalls (And How to Avoid Them)

Here are mistakes we made so you don't have to:

1. Trying to Do Everything at Once

Mistake: Tried to implement CI/CD, switch cloud providers, and migrate to microservices simultaneously.

Result: 3 months of chaos. Nothing worked.

Lesson: One thing at a time. CI first. Then CD. Then optimization.

2. Not Testing the Deployment Process

Mistake: Tested code extensively. Never tested deployment script.

Result: Deployment script had a bug. Deleted production database. (We had backups, but still...)

Lesson: Deployment scripts ARE code. Test them.

3. Ignoring Build Times

Mistake: Let CI pipeline grow to 15 minutes. "It's fine, it's automated!"

Result: Developers stopped making small commits. Batched changes. More bugs.

Lesson: Fast feedback is critical. Keep CI under 5 minutes or developers will avoid it.

4. Not Planning for Failure

Mistake: Assumed deployments would always work.

Result: When deployment failed at 3 PM on Friday, we had no rollback plan.

Lesson: Every deployment needs a rollback plan. Test it monthly.

5. Skipping Documentation

Mistake: "The pipeline is self-explanatory!"

Result: When I went on vacation, team couldn't deploy. Called me 6 times.

Lesson: Document your CI/CD process. Runbooks for common issues. Onboard new team members.

The 30-Day CI/CD Implementation Plan

Based on our experience, here's a realistic timeline:

Week 1: Foundation

  • Day 1-2: Choose CI/CD platform
  • Day 3-4: Set up basic pipeline (test only)
  • Day 5: Add code quality checks (linting, formatting)

Week 2: Testing

  • Day 8-9: Integrate test suite into CI
  • Day 10-11: Add test coverage reporting
  • Day 12: Optimize for speed (caching, parallelization)

Week 3: Deployment

  • Day 15-16: Set up staging environment
  • Day 17-18: Implement automated deployment to staging
  • Day 19: Test rollback procedures

Week 4: Production

  • Day 22-23: Deploy to production (manual trigger)
  • Day 24-25: Add monitoring and alerts
  • Day 26: Document everything
  • Day 29-30: Enable auto-deployment, celebrate!

Cost Analysis: Is CI/CD Worth It?

Upfront costs:

  • CI/CD platform: $150/month (GitHub Actions was free for us)
  • Additional infrastructure: $300/month (staging environment)
  • Developer time: 80 hours (setup and migration)
  • Total first month: ~$1,000 (including dev time at $100/hr)

Monthly savings:

  • Developer time (no manual deploys): 15 hours × $100/hr = $1,500
  • Reduced downtime: ~$3,000 (estimated revenue impact)
  • Faster feature delivery: Hard to quantify, but significant
  • Total monthly savings: $4,500+

ROI: Positive after first month. By month 3, saved $10,000+.

Final Thoughts: The Cultural Shift

Here's what nobody tells you: CI/CD isn't a technical problem. It's a cultural one.

The hardest part wasn't writing YAML files. It was convincing developers to:

  • Write tests for everything
  • Trust automated deployments
  • Stop working weekends for deployments
  • Embrace smaller, more frequent changes

It took 6 months for the team to fully trust the system. Now they can't imagine going back.

My advice:

  1. Start small. Prove value quickly.
  2. Celebrate wins. First automated deployment is a big deal!
  3. Be patient. Cultural change takes time.
  4. Lead by example. If you trust the pipeline, others will too.
  5. Keep improving. CI/CD is never "done."

Today, we deploy dozens of times per day with confidence. Our users get features faster. Our developers are happier. And I never work Friday at 5 PM anymore.

Was it worth the 80-hour investment?

Absolutely. I got my evenings and weekends back. So did my team.


Want help implementing CI/CD for your team? The Daullja DevOps team specializes in building robust deployment pipelines. We've helped 50+ companies go from manual deployments to fully automated CI/CD.

Collaborative tech team working together

Building Your Remote Tech Team

Access world-class talent without geographical boundaries

Success Metrics That Matter
300+
companies
Active IT Companies in Kosovo
60-70%
savings
Cost Savings vs US Developers
29
years
Average Age - Young Workforce
Modern technology and innovation
Innovation & Technology

Ready to Build Your Dream Team?

Let's discuss how we can connect you with top Eastern European tech talent to accelerate your growth.

Get Started Today