Skip to content
·11 min read

GitHub Actions for Automated Builds and Deploys in Vibe Coding

How to set up GitHub Actions so your vibe-coded app gets tested, built, and deployed every time you push code

Share

GitHub Actions deploy your code automatically every time you push to a repository. If you are building with AI tools and pushing to GitHub, you already have the infrastructure for automated builds and deploys sitting right there, unused. This tutorial walks through setting it up with real YAML files and warnings about the mistakes that cost hours.

Here is the honest context. Platforms like Vercel and Netlify already handle build-and-deploy when you push code. They do this well. But they do not run your tests. They do not lint your code. They do not catch the broken import that your AI tool introduced three commits ago. GitHub Actions fills that gap by running whatever checks you want before your code reaches production. With 92% of US developers now using AI tools daily and 46% of code being AI-generated, automated quality gates are not optional anymore. They are the safety net between AI output and your users.

What GitHub Actions Actually Are

GitHub Actions is a CI/CD system built into GitHub. CI stands for continuous integration (automatically testing code when it changes) and CD stands for continuous deployment (automatically shipping code after tests pass). You define workflows in YAML files, and GitHub runs them on its own servers whenever a trigger event happens.

The key concept is that a workflow is just a series of steps that run on a fresh virtual machine. That machine has nothing installed except a base operating system. Your workflow has to set everything up from scratch: install Node.js, install your dependencies, then run whatever commands you need. This is different from your local machine, where everything is already configured.

This "fresh machine" model is actually a strength. If your workflow passes, you know the build works on a clean environment, not just on your laptop with its unique collection of globally installed packages and cached files.

EXPLAINER DIAGRAM: A flowchart showing the GitHub Actions workflow lifecycle. On the left, a developer icon pushes code to GitHub (represented by a push arrow to a repository icon). From the repository, an arrow labeled TRIGGER points to a box labeled WORKFLOW containing three sequential steps: step 1 INSTALL with a Node.js logo, step 2 LINT AND TEST with a checkmark and X icon, and step 3 BUILD with a gear icon. From the workflow box, two paths emerge: a green arrow labeled ALL PASS leading to a DEPLOY box with a rocket icon, and a red arrow labeled ANY FAIL leading to a STOP box with an alert icon that sends a notification back to the developer.
GitHub Actions runs your workflow on a fresh virtual machine every time you push code.

The Workflows Directory

GitHub Actions looks for workflow files in one specific location: .github/workflows/ at the root of your repository. If this directory does not exist, create it. Every .yml or .yaml file in this directory becomes a workflow that GitHub can run.

mkdir -p .github/workflows

You can have multiple workflow files. A common pattern is one for CI (testing and linting) and one for deployment. Each file is independent and can have different triggers.

A Complete CI Workflow for Next.js

Here is a full ci.yml file that installs dependencies, runs linting, runs tests, and builds your Next.js app. This catches the majority of problems that AI-generated code introduces.

name: CI

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

jobs:
  ci:
    runs-on: ubuntu-latest

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

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

Save this as .github/workflows/ci.yml, push it to GitHub, and the workflow will run immediately. Let me walk through what each section does.

The on block defines triggers. This workflow runs on every push to the main branch and on every pull request that targets main. The pull_request trigger is the important one because it runs checks before code gets merged. If the workflow fails on a pull request, GitHub shows a red X next to it, warning you not to merge.

The runs-on field tells GitHub which type of virtual machine to use. ubuntu-latest is the standard choice and is free for public repositories with generous limits for private ones.

The steps array is where the real work happens. Each step runs sequentially, and if any step fails, the entire workflow stops. This means your linter runs before your tests, and your tests run before the build. If linting fails, you do not waste time waiting for a build.

The cache: 'npm' line on the Node.js setup step is critical for speed. Without it, every workflow run downloads all your dependencies from scratch. With caching, npm stores the downloaded packages between runs and only fetches what has changed. On a typical Next.js project, this cuts workflow time from three to four minutes down to one to two minutes.

Key Takeaway

The npm ci command is not the same as npm install. It does a clean install based on your package-lock.json file, which ensures your CI environment matches exactly what you tested locally. Using npm install in CI can introduce version drift that causes mysterious failures. Always use npm ci in your workflows.

Now that you have the core CI workflow in place, the next step is controlling when it runs. GitHub Actions gives you fine-grained control over workflow triggers.

New to Deployment?

Learn what happens when you take your app from localhost to the internet.

Start with the basics

Understanding Triggers

The on block supports many more triggers than just push and pull request. Here are the ones that matter most for vibe coders.

push runs when code is pushed to matching branches. You can also filter by file paths, so the workflow only runs when specific directories change:

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'

pull_request runs when a PR is opened, updated, or reopened against matching branches. This is your pre-merge safety net.

workflow_dispatch lets you trigger a workflow manually from the GitHub UI. Useful for deploy workflows you want to run on demand:

on:
  workflow_dispatch:

schedule runs on a cron schedule. This is useful for nightly builds or periodic dependency checks, but most vibe coders will not need it immediately.

Storing Secrets in GitHub

Your workflows will need environment variables that should not be in your code. API keys, deploy tokens, database URLs. GitHub has a built-in secrets system for this.

Go to your repository on GitHub, click Settings, then Secrets and variables, then Actions. Click "New repository secret" and add each value. In your workflow file, you reference secrets like this:

- name: Build
  run: npm run build
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
    NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}

One warning: secrets are not available in pull requests from forks. This is a security feature. For most vibe coding projects where you are the only contributor, it will not affect you.

Common Mistake

Hardcoding API keys or tokens directly in your workflow YAML file. Your workflow files are committed to your repository, which means anyone with access to the repo can see them. Always use GitHub Secrets for sensitive values and reference them with the ${{ secrets.SECRET_NAME }} syntax. If you accidentally commit a secret, rotate it immediately because Git history is permanent.

Deploying to Vercel via GitHub Actions

Vercel deploys automatically when you push to a connected repository. So why would you use GitHub Actions for deployment? Because you want the deploy to happen only after your tests pass. Without Actions, a push to main triggers both your CI checks and the Vercel deploy simultaneously. If your tests fail, Vercel has already deployed the broken code.

Here is a deploy workflow that waits for CI to pass first:

name: Deploy

on:
  workflow_run:
    workflows: [CI]
    types: [completed]
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

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

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

The difference between deploying with and without GitHub Actions comes down to one thing: whether broken code can reach production.

EXPLAINER DIAGRAM: A two-lane comparison diagram. The top lane labeled WITHOUT GITHUB ACTIONS shows a single push event splitting into two parallel arrows, one going to a CI CHECKS box and one going to a VERCEL DEPLOY box, with a red warning label reading DEPLOY HAPPENS EVEN IF TESTS FAIL. The bottom lane labeled WITH GITHUB ACTIONS shows a push event going to a CI CHECKS box first, then a decision diamond labeled PASS with a green arrow going to VERCEL DEPLOY and a red arrow labeled FAIL going to a BLOCKED icon. A green banner on the bottom lane reads DEPLOY ONLY AFTER ALL CHECKS PASS.
GitHub Actions lets you gate deployments behind passing tests, preventing broken code from reaching production.

The workflow_run trigger makes this deploy workflow wait for the CI workflow to complete. The if condition ensures it only deploys when CI succeeded. If your tests fail, no deployment happens.

For Netlify, the pattern is similar. Use the netlify/actions/cli@master action with your Netlify auth token and site ID stored as secrets.

The key step for either platform is to disable automatic deploys in the platform's settings. In Vercel, go to your project settings, find the Git section, and turn off "Automatically deploy on push." This way, only your GitHub Actions workflow triggers deploys, and it only does so after tests pass.

Caching Dependencies for Speed

The cache: 'npm' option in the Node.js setup step handles the basics, but you can go further. If your project has a heavy build step that produces the same output for the same inputs, you can cache the build output too:

- name: Cache Next.js build
  uses: actions/cache@v4
  with:
    path: .next/cache
    key: nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ hashFiles('src/**') }}
    restore-keys: |
      nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-
      nextjs-${{ runner.os }}-

This caches the .next/cache directory, which Next.js uses to store incremental build results. On subsequent runs, the build step skips work if your source files have not changed much. For large projects, this cuts build times by 40% to 60%. The restore-keys provide fallback matches so partial cache hits still help.

What This Means For You

GitHub Actions is free infrastructure that sits between your AI-generated code and your users. Setting it up takes 15 minutes and pays for itself the first time it catches a broken build before it reaches production.

  • If you are an indie hacker: Start with the basic CI workflow. Add linting and a build check. You do not need tests on day one, but having the workflow in place means you can add tests later without changing your deployment process. The discipline of "green checks before merge" will save you from shipping broken features to paying customers.
  • If you are a senior developer adopting AI tools: You already know why CI/CD matters, but you might be surprised how often AI-generated code passes local checks and fails in CI. The clean environment of a GitHub Actions runner exposes hidden dependencies on local state that your machine masks. Treat your CI workflow as a trust-but-verify layer for AI output.
  • If you are a team lead: Require passing CI checks before merging pull requests. In your GitHub repository settings, go to Branches, add a branch protection rule for main, and check "Require status checks to pass before merging." This single setting prevents anyone (including AI tools with push access) from shipping code that breaks the build.
Worried About AI Code Security?

Automated builds are step one. Learn what else to check before shipping.

Read the security guide
PJ
Pranay Joshi

20+ years building products at scale. VP of Product & Engineering, startup founder, and AI coach. Helping dreamers turn ideas into reality with vibe coding.

The Tuesday Shipping Report

Every Tuesday, one focused email:

  • - The tool or technique that's actually working right now
  • - A real problem from the community (and how to solve it)
  • - What changed this week in the vibe coding landscape

Read by 1,000+ founders, developers, and creators building with AI. Free forever. No spam.