Agentic Coding

AI-Assisted CI/CD Workflows: A Practical Guide

Build a production CI/CD pipeline using AI coding assistants — covering workflow generation, secrets handling, Docker layering, and deployment gates with real GitHub Actions YAML.

May 31, 2026 10 min read
AI-Assisted CI/CD Workflows: A Practical Guide

CI/CD pipelines are the kind of infrastructure that everyone agrees matters and most teams underinvest in. Writing GitHub Actions YAML from scratch is tedious, the indentation rules are unforgiving, and the feedback loop (push, wait 3 minutes, read a cryptic error) is slow. AI coding assistants cut that loop dramatically. At Laxaar, we've used them to draft and iterate pipeline configurations for Node.js, Python, and containerised services, and the time savings are real.

This tutorial builds a complete CI/CD pipeline for a Node.js API: lint, test, build Docker image, push to a registry, and deploy to a staging environment. We'll use GitHub Actions and Claude Code as the AI assistant. Every YAML block is production-quality and runs without modification on a standard GitHub-hosted runner.

What you'll build

Step 1: Brief the AI on your pipeline requirements

The quality of AI-generated YAML is directly proportional to the quality of the brief. A vague prompt like "write a CI pipeline for my Node app" produces a generic template that won't match your project's actual shape.

Write a ci-brief.md before opening your AI assistant:

# CI/CD brief

App: Node.js 20 + TypeScript, compiled to dist/.
Package manager: npm (package-lock.json present).
Test runner: Vitest.
Linter: ESLint + Prettier check.
Docker: single-stage for dev, multi-stage for production.
Registry: GitHub Container Registry (ghcr.io).
Environments: staging (auto-deploy on main), production (manual approval).
Secrets needed: GHCR_TOKEN, STAGING_SSH_HOST, STAGING_SSH_KEY, STAGING_SSH_USER.
Constraints: fail fast on lint errors, don't run deploy if tests fail,
  cache node_modules between runs.

Pass this to the AI at the start of the session and keep it open in a separate window. Every time you review AI output, check it against this brief.

One specific thing to add: tell the AI the ubuntu-latest runner version you're targeting. GitHub periodically bumps this to a new Ubuntu LTS, and the action versions (actions/checkout@v4, actions/setup-node@v4) need to match.

Step 2: Generate and validate the CI workflow

Ask for the CI job first, separate from the deploy job. Smaller scope, faster review.

Generate a GitHub Actions workflow for the CI stage only (no deploy yet).
It should: check out code, set up Node 20 with npm cache, run npm ci,
run ESLint, run Prettier check, run Vitest with coverage.
Fail fast if any step fails. Use ubuntu-latest.

Expected output — this is the pattern to check against:

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

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

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        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: Lint
        run: npx eslint . --max-warnings 0

      - name: Format check
        run: npx prettier --check .

      - name: Test
        run: npx vitest run --coverage

      - name: Upload coverage
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage
          path: coverage/
          retention-days: 7

Three things to verify manually before committing this:

  1. --max-warnings 0 matches your ESLint config. If your repo has existing warnings you haven't fixed, this will break the build immediately.
  2. npx vitest run is the non-watch mode. npx vitest alone starts the interactive watcher and the job hangs indefinitely.
  3. The cache: 'npm' key assumes package-lock.json is committed. If it's in .gitignore, caching silently fails.

Push this workflow and watch it run before adding the Docker stage. Don't stack configuration on top of untested configuration.

Step 3: Add Docker build and registry push

Multi-stage Docker builds keep production images small. The AI knows this pattern well, but it needs your project's specific paths.

Prompt:

Add a Docker build stage to ci.yml that runs after the CI job passes.
Build a multi-stage Dockerfile: stage 1 installs deps and compiles TypeScript,
stage 2 is node:20-alpine with only dist/ and node_modules (production deps only).
Push to ghcr.io/${{ github.repository }} tagged with the git SHA.
Use GHCR_TOKEN secret for authentication.
Only run this job on pushes to main, not on PRs.

The Dockerfile the AI should produce:

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx tsc

FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]

And the workflow addition:

  docker:
    runs-on: ubuntu-latest
    needs: ci
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.sha }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

The cache-from: type=gha and cache-to: type=gha,mode=max lines are important. They wire Docker layer caching through GitHub Actions cache, which cuts build time by 60–70% on subsequent runs when only application code changes. The AI doesn't always include these, so check for them.

needs: ci is the dependency that prevents a Docker push when tests are failing. Don't remove it.

Step 4: Set up deployment gates and environment protection

Automatic staging deploys and manual production deploys are the pattern that works for most teams. GitHub Environments enforce this without extra tooling.

Create the environments in your GitHub repo: Settings → Environments → New environment. Create staging (no protection rules) and production (require review from your team lead).

Then prompt the AI:

Add a deploy-staging job that runs after docker, deploys to staging via SSH.
The deployment command is: ssh user@host "docker pull image && docker stop app || true && docker run -d --name app -p 3000:3000 image"
Use STAGING_SSH_HOST, STAGING_SSH_KEY, STAGING_SSH_USER secrets.
Set environment: staging so GitHub tracks the deployment.
  deploy-staging:
    runs-on: ubuntu-latest
    needs: docker
    environment: staging
    steps:
      - name: Set up SSH
        uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}

      - name: Deploy to staging
        env:
          HOST: ${{ secrets.STAGING_SSH_HOST }}
          USER: ${{ secrets.STAGING_SSH_USER }}
          IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}
        run: |
          ssh -o StrictHostKeyChecking=no $USER@$HOST "
            docker pull $IMAGE &&
            docker stop app 2>/dev/null || true &&
            docker rm app 2>/dev/null || true &&
            docker run -d --name app -p 3000:3000 --restart unless-stopped $IMAGE
          "

The || true after docker stop and docker rm prevents the job from failing when no container is running (first deployment). This is a real-world detail the AI sometimes omits — worth checking.

For production, add a manual-approval gate by adding environment: production to the production job. GitHub will require a reviewer approval before the job runs.

Step 5: Iterate on failures using AI-assisted debugging

When a pipeline step fails, paste the exact log output to the AI alongside the relevant YAML block. Don't summarise the error — paste it verbatim.

A common failure pattern when starting with GHCR:

Error: denied: permission_denied: write_package

This means the GITHUB_TOKEN (if you're using that instead of GHCR_TOKEN) doesn't have packages: write permission. The fix is the permissions block on the job:

    permissions:
      contents: read
      packages: write

Prompt to debug effectively:

Here's the GitHub Actions log for the docker job:
[paste full log]

Here's the job YAML:
[paste YAML]

What's causing the permission error and what's the minimal fix?

"Minimal fix" is the key phrase. Without it, the AI may suggest restructuring the entire workflow when the actual fix is two lines.

At Laxaar we keep a running document of pipeline failure patterns we've hit and their fixes. Feeding these patterns to the AI at the start of new pipeline sessions prevents it from suggesting approaches we've already ruled out.

Common pitfalls

Pinning to @latest action tags. Action tags like actions/checkout@latest can silently introduce breaking changes. Always pin to a major version tag (@v4) or a specific commit SHA for security-sensitive workflows.

Storing secrets in workflow YAML. Any value hardcoded in .github/workflows/ is visible to anyone with repo read access. Even non-sensitive configuration values that vary per environment belong in GitHub Secrets or Variables, not in YAML.

Not testing the Docker image locally before pushing. If the image doesn't start correctly, the deployment job succeeds but the app is down. Add a smoke test step: docker run --rm image node dist/index.js --version validates the image starts without deploying it.

Running deploy on every branch. The if: github.ref == 'refs/heads/main' guard is non-negotiable. Without it, every feature branch push triggers a staging deployment, and you end up with staging running whatever the last developer pushed.

Frequently Asked Questions

How do I handle different environment variables per environment?

Use GitHub Environment variables (not secrets) for non-sensitive config that changes per environment — API base URLs, feature flags, log levels. Secrets are for credentials. Both are scoped per environment when you use the environment: key in your job.

Can the AI generate pipelines for GitLab CI or CircleCI too?

Yes. The prompt structure is the same — give a detailed brief including the platform-specific syntax requirements. GitLab CI uses .gitlab-ci.yml with a different job syntax; CircleCI uses config.yml with orbs. Mention the platform explicitly and paste the relevant docs section if the AI produces an unfamiliar pattern.

How do I cache Docker layers efficiently?

The type=gha cache backend is the easiest approach for GitHub Actions. For higher cache hit rates, order your Dockerfile so the layers that change least (system packages, production npm ci) come before the layers that change most (application code). The AI often gets this right if you ask it to "order layers for maximum cache reuse."

What's the right way to handle database migrations in CI/CD?

Run migrations as a pre-deploy step, not as part of the app startup. This keeps the migration atomic and makes rollback predictable. Ask the AI to add a migration job between docker and deploy-staging that runs npx prisma migrate deploy (or your equivalent) against the target environment.

How do I notify the team when a deployment fails?

Add a notification step with if: failure() at the end of deploy jobs. Slack webhooks via slackapi/slack-github-action are the most common approach. The AI can generate this in one prompt — give it your Slack webhook secret name and the channel.

Should CI pipelines be written by hand or generated by AI?

Both. Use the AI to generate the initial pipeline and handle boilerplate. Then own the YAML yourself — understand every line, test it, and evolve it manually as the project changes. A pipeline you don't understand is infrastructure debt.


Laxaar helps teams move from manual deployments to fully automated pipelines across cloud and containerised environments. If your delivery process needs work, visit our automation expertise page or contact us to talk through your setup.

CI/CDGitHub ActionsAI Coding
Grow your business with us

Take your business to the next level.

Tell us what you're building. We'll come back inside one business day with a fixed scope, timeline, and team — or an honest “this isn't a fit”.

ENGINEERING PHILOSOPHY

Code is useless if it's not comprehensible to those who maintain it. We write code the next person can actually understand.