DevOpsCI/CDGitHub ActionsDockerStartups

DevOps CI/CD for Startups: A Practical Setup Guide

A practical guide to setting up a complete CI/CD pipeline for a startup — GitHub Actions, Docker, staging environments, and zero-downtime production deployments.

Softotic Engineering/28 January 2025/3 min read

DevOps CI/CD for Startups: A Practical Setup Guide

"Move fast and break things" is a startup saying — but in production, breaking things is expensive. A solid CI/CD pipeline lets you move fast and ship reliably. Here's the setup we implement for Softotic clients from day one.

Why CI/CD from Day One?

Every day without CI/CD is a day you're accumulating manual deployment risk. We've seen teams:

  • Forget to run tests before deploying
  • Ship code that conflicts with a colleague's changes
  • Deploy to production without testing on staging first
  • Spend hours debugging "works on my machine" issues

A CI/CD pipeline eliminates all of these.

The Stack We Recommend

  • GitHub Actions — free for public repos, generous limits for private, native Git integration
  • Docker — environment consistency from dev to prod
  • Docker Hub / GitHub Container Registry — image registry
  • VPS for staging (DigitalOcean, Hetzner) — cheap, controllable
  • AWS ECS or a VPS for production

Pipeline Structure

code
develop branch push → [CI] → deploy to staging
main branch push → [CI] → deploy to production
PR opened → [CI] → run tests and lint (no deploy)

GitHub Actions: The Complete Workflow

yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage
      - run: npm run build

  deploy-staging:
    needs: test
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:staging

      - name: Deploy to staging via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: deploy
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            docker pull ghcr.io/${{ github.repository }}:staging
            docker stop app-staging || true
            docker run -d --rm --name app-staging \
              -p 3000:3000 \
              --env-file /home/deploy/.env.staging \
              ghcr.io/${{ github.repository }}:staging

  deploy-production:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Build and push production image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
      # ... deploy to production

Zero-Downtime Deployments

Use Docker with a health check and rolling restart:

dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/api/health || exit 1

With Nginx as a reverse proxy:

  1. Start new container on a different port
  2. Health check passes
  3. Nginx upstream switches to new container
  4. Old container stops

Environment Variables

Never commit secrets. Use:

  • GitHub Actions Secrets — for CI/CD variables
  • __INLINE_CODE_0__ on your local machine (git-ignored)
  • AWS SSM Parameter Store or Doppler in production
code
# .env.example (commit this)
DATABASE_URL=postgresql://user:pass@host:5432/dbname
REDIS_URL=redis://localhost:6379
NEXT_PUBLIC_SITE_URL=https://example.com

Monitoring After Deployment

Add post-deploy verification:

yaml
- name: Smoke test production
  run: |
    sleep 10
    curl -f https://www.example.com/api/health || exit 1

Also set up:

  • Uptime monitoring: UptimeRobot (free) or Better Uptime
  • Error tracking: Sentry
  • Logs: Datadog or Papertrail

Conclusion

A CI/CD pipeline is infrastructure, not overhead. Set it up on day one and ship with confidence from the first deployment to the thousandth.

Need DevOps help? Softotic's cloud team sets up production-grade pipelines for startups and enterprises.