GitHub Actions: Replaces Travis CI?

devops ci-cd github

Travis CI dominated open source CI for a decade. GitHub Actions arrived, and the migration began. For many projects, Actions is now the better choice.

GitHub Actions Basics

# .github/workflows/ci.yml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
          
      - name: Install dependencies
        run: |
          pip install --upgrade pip
          pip install -r requirements.txt
          
      - name: Run tests
        run: pytest

Push to GitHub, CI runs. That simple.

vs Travis CI

Travis CI Configuration

# .travis.yml
language: python
python:
  - "3.10"
  
install:
  - pip install -r requirements.txt
  
script:
  - pytest

Key Differences

FeatureGitHub ActionsTravis CI
Pricing (OSS)FreeLimited free
Pricing (Private)2000 min/month freePaid
RunnersGitHub-hosted or self-hostedTravis-hosted
Ecosystem10,000+ marketplace actionsLimited
Matrix builds
CachingBuilt-inBuilt-in
SecretsEncrypted, easyEncrypted
YAML complexityHigherLower

Actions Advantage: Marketplace

jobs:
  test:
    steps:
      # Official actions
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
      
      # Community actions
      - uses: codecov/codecov-action@v3
      - uses: slackapi/slack-github-action@v1

Thousands of pre-built actions. Don’t reinvent the wheel.

Matrix Builds

Test across multiple versions:

jobs:
  test:
    strategy:
      matrix:
        python-version: ['3.8', '3.9', '3.10', '3.11']
        os: [ubuntu-latest, macos-latest]
        
    runs-on: ${{ matrix.os }}
    
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - run: pytest

8 jobs (4 Python × 2 OS) in parallel.

Caching Dependencies

jobs:
  test:
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-python@v4
        with:
          python-version: '3.10'
          cache: 'pip'  # Built-in pip caching
          
      - run: pip install -r requirements.txt

Or manual caching:

- uses: actions/cache@v3
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

Deployment

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to production
        uses: appleboy/ssh-action@v0.1.0
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /app
            git pull
            docker-compose up -d --build

Docker Builds

jobs:
  build:
    steps:
      - uses: actions/checkout@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}
          
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: myapp:latest

Secrets and Environment Variables

jobs:
  deploy:
    environment: production  # Requires approval
    
    steps:
      - name: Deploy
        run: ./deploy.sh
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

Conditional Jobs

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: pytest
  
  deploy:
    needs: test  # Wait for test to pass
    if: github.ref == 'refs/heads/main'  # Only on main
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Reusable Workflows

# .github/workflows/reusable.yml
name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      python-version:
        required: true
        type: string

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ inputs.python-version }}
      - run: pytest
# .github/workflows/ci.yml
name: CI
on: push

jobs:
  test-38:
    uses: ./.github/workflows/reusable.yml
    with:
      python-version: '3.8'
      
  test-310:
    uses: ./.github/workflows/reusable.yml
    with:
      python-version: '3.10'

Self-Hosted Runners

For custom environments or private networks:

jobs:
  build:
    runs-on: self-hosted
    steps:
      - run: ./build.sh

Migration from Travis

Most patterns translate directly:

TravisGitHub Actions
language: pythonsetup-python@v4
python: ["3.8", "3.9"]matrix.python-version
before_installEarly steps
installrun: pip install
scriptrun: pytest
after_successConditional steps
deploySeparate job with needs

Should You Migrate?

Migrate if:

Stay if:

Final Thoughts

GitHub Actions won the CI/CD war through integration. Living inside GitHub means less context switching, better security with secrets, and a massive ecosystem.

For new projects on GitHub, Actions is the default choice. For existing Travis projects, migrate when you have time—but you probably should.


CI should be invisible. Actions makes it so.

All posts