Blog
October 18, 2018 Marie H.

GitHub Actions: A First Look at the New CI/CD

GitHub Actions: A First Look at the New CI/CD

GitHub Actions: A First Look at the New CI/CD

GitHub announced Actions at GitHub Universe last week and I've been in the limited beta since Tuesday. I've migrated a few small Go projects over to it and have enough experience to say something useful. Short version: it's good, it's rough in places, and it's worth paying attention to.

Why This Is Interesting

The pitch is simple: your code is already on GitHub, so why is your CI somewhere else?

Every CI system I've used — Jenkins, CircleCI, CodeBuild, Travis — follows the same basic integration pattern. You connect an external service to your GitHub repo, configure webhooks or OAuth, define your build in whatever format that service uses, and maintain two separate things: your code and your build config. It works, but the seam is always a little awkward. Secrets live in two places. Config drift happens. Onboarding a new engineer means getting them access to both GitHub and the CI platform.

Actions collapses this. Your workflow definition lives in .github/workflows/ in your repo. It's version-controlled alongside your code. When you review a PR, you see the workflow change in the same diff as the code change. Secrets are stored in GitHub's repo settings and injected at runtime. There's one system to manage.

The other interesting part is the event model. Actions can trigger on almost any GitHub event: push, pull request, release, issue comment, repository dispatch. You're not limited to "run on every push." You can run a workflow when a PR is labeled, when a release is published, when an issue is opened. That's more expressive than most CI systems I've used.

Basic Workflow Syntax

Workflows are YAML files in .github/workflows/. Here's a CI workflow for a Go project — lint, test, build on every push and pull request:

name: CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Set up Go
        uses: actions/setup-go@v1
        with:
          go-version: '1.11'

      - name: Install golint
        run: go get -u golang.org/x/lint/golint

      - name: Lint
        run: golint -set_exit_status ./...

      - name: Test
        run: go test -v -race ./...

      - name: Build
        run: go build -v ./...

The top-level keys are on, jobs. Under jobs, you define one or more jobs, each with a runs-on (the runner OS) and a list of steps. Each step either uses a community action or runs a shell command directly.

actions/checkout and actions/setup-go are actions from GitHub's official actions org. uses: actions/checkout@v1 pins to the v1 tag — you'll want to pin rather than float on master for anything you care about stability on.

Community Actions and the Marketplace

The uses key is where the ecosystem play happens. Anyone can publish an action as a public GitHub repo, and you reference it with owner/repo@ref. There's already a marketplace at github.com/marketplace with actions for deploying to AWS, sending Slack notifications, running Docker builds, publishing npm packages, and a bunch more.

For our Go project, adding Docker build and push to ECR looks like this:

      - name: Configure AWS credentials
        uses: actions/aws-cli@master
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: us-east-1

      - name: Login to ECR
        run: |
          $(aws ecr get-login --no-include-email --region us-east-1)

      - name: Build and push Docker image
        run: |
          IMAGE_URI=123456789.dkr.ecr.us-east-1.amazonaws.com/myapp
          docker build -t $IMAGE_URI:${{ github.sha }} .
          docker push $IMAGE_URI:${{ github.sha }}

Secrets referenced with ${{ secrets.SECRET_NAME }} come from the repo's Settings > Secrets. They're masked in logs.

How It Compares

vs. CircleCI: CircleCI is more mature and has better support for things like caching, parallelism, and test splitting. The orbs ecosystem is more developed than the Actions marketplace at this point. If your team is already happy on CircleCI, there's no urgent reason to migrate. The main appeal of Actions is consolidation — one less external service, especially valuable for teams with many small repos.

vs. Jenkins: Jenkins has more flexibility and a huge plugin ecosystem, but the operational overhead is real. Running Jenkins means running Jenkins — patching it, managing agents, dealing with plugin compatibility. Actions is fully managed. For most teams, the flexibility of Jenkins isn't worth the maintenance cost, and Actions covers the common CI/CD cases well.

vs. CodeBuild: If you're on AWS and already using CodePipeline, CodeBuild makes sense — the integration is tight. But the config is more verbose and the feedback loop (push to GitHub, wait for CodePipeline to poll, watch CodeBuild logs in a separate console) is clunky compared to seeing results inline on a PR. Actions wins on developer experience.

Honest Assessment: It's Still Beta

I want to be straight about where the rough edges are.

Caching is limited. There's no first-class dependency caching yet. Go module downloads, npm installs, pip installs — they run fresh every time. This makes builds slower than they need to be. CircleCI's caching story is much better. GitHub has said caching is coming, and I believe them, but it's not there now.

Artifacts are rough. You can upload artifacts between jobs, but the interface is basic. No retention policies, limited visibility in the UI.

No local runner. The only way to test a workflow is to push to GitHub and wait. For a 5-step workflow this is fine; for a 20-step workflow it's painful. Tools like act are starting to emerge in the community but they're not official.

Matrix builds are limited. Running the same job across multiple Go versions or OS combinations requires some config gymnastics. The matrix strategy works but it's not polished.

The ecosystem is thin. The marketplace has actions, but quality varies. You'll end up writing shell commands for things that should have first-class actions. That'll improve with time.

None of these are dealbreakers for the use cases I've tried. For a straightforward Go project — lint, test, build, push a Docker image — Actions works well today. For something complex with heavy caching requirements or sophisticated parallelism, you might want to wait for the GA release.

Should You Evaluate It?

If your team is already on GitHub, yes, worth a look. The integration story is genuinely better than maintaining a separate CI service. The event-driven model is expressive. Community actions are going to get better.

If you're evaluating CI for the first time, I'd probably still recommend CircleCI for production use right now — the feature set is more complete and the documentation is better. But check back in six months. GitHub has the distribution advantage and the resources to close the gap quickly.

I'm keeping my personal projects on Actions. The friction of having CI live somewhere other than my repo has always bothered me in a low-grade way, and Actions fixes that even in beta. Good enough for me.