# Deploy

> Deployment rules - GitHub Pages, Vercel, Netlify, build optimization, CI/CD, env vars, custom domains

- Skill: `14bryanespinoza/deploy` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/deploy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/deploy/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/deploy

---


# Deploy — Rules and Conventions

---

## 1. Philosophy

1. **Platform-agnostic build** — Same build output deploys anywhere. Platform config only.
2. **Immutable deployments** — Every deploy = unique URL. Rollback = switch URL.
3. **Env vars in platform** — Never in repo. Platform dashboard or secrets manager.
4. **Preview deploys on PR** — Every PR gets a preview URL. Review before merge.
5. **Observability built-in** — Logs, metrics, alerts from day one.

---

## 2. Minimum Versions

| Technology | Minimum Version |
| ---------- | --------------- |
| Node.js    | 22+             |
| pnpm       | 11+             |
| Git        | 2.43+           |
| GitHub CLI | 2.40+           |

---

## 3. Preparation for Deploy

### Build locally first

```bash
# Type check
pnpm typecheck

# Lint + format check
pnpm lint
pnpm format:check

# Build
pnpm build

# Verify output
ls -la dist/  # or .vercel/output, .netlify, etc.
```

### Git hygiene

```bash
# Ensure clean working tree
git status

# Tag release (from main)
git switch main
git pull origin main
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0
```

---

## 4. Platform Strategy

| Platform             | Best For                          | Static/SSR  | Config File                   |
| -------------------- | --------------------------------- | ----------- | ----------------------------- |
| **Vercel**           | Next.js, Astro SSR, React SPA     | Both        | `vercel.json`                 |
| **Netlify**          | Astro, Vite, static sites         | Both        | `netlify.toml`                |
| **GitHub Pages**     | Static only (Astro SSG, Vite SPA) | Static only | `.github/workflows/pages.yml` |
| **Cloudflare Pages** | Static + edge functions           | Both        | `wrangler.toml` / dashboard   |

> **Rule**: Pick ONE primary platform. Others only for specific
> needs (edge, cost, team preference).

---

## 5. Vercel (Canonical SSR/Static)

### `vercel.json`

```json
{
  "buildCommand": "pnpm build",
  "outputDirectory": "dist",
  "devCommand": "pnpm dev",
  "installCommand": "pnpm install",
  "framework": "astro",
  "regions": ["iad1"],
  "functions": {
    "src/pages/api/**/*.ts": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ],
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```

### Vercel CLI

```bash
# Preview deploy (auto on PR)
vercel

# Production deploy
vercel --prod

# List deployments
vercel ls

# Rollback
vercel rollback <deployment-url>
```

### Rules

- **`framework: "astro"`** — auto-detects build config
- **`outputDirectory: "dist"`** — matches Astro/Vite default
- **Headers** — cache static assets long, HTML short
- **Rewrites** — SPA fallback for client-side routing

---

## 6. Netlify (Canonical Static/Hybrid)

### `netlify.toml`

```toml
[build]
  command = "pnpm build"
  publish = "dist"
  functions = "netlify/functions"

[build.environment]
  NODE_VERSION = "22"
  PNPM_VERSION = "11"

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
  conditions = {Role = ["admin"]}  # SPA fallback

[functions]
  node_bundler = "esbuild"
  included_files = ["src/**/*"]
```

### Netlify CLI

```bash
# Preview deploy
netlify deploy

# Production deploy
netlify deploy --prod

# Rollback
netlify rollback <deploy-id>
```

### Rules Nettly

- **`publish = "dist"`** — matches Astro/Vite default
- **Edge functions** in `netlify/functions/` — see Netlify docs
- **Headers** — security headers + asset caching
- **Redirects** — SPA fallback with 200 status

---

## 7. GitHub Pages (Static Only)

### Workflow (`.github/workflows/pages.yml`)

```yaml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4
```

### Rules GH

- **Static only** — no SSR, no server functions
- **`output: "static"`** in Astro config, or Vite SPA
- **Custom domain** — configure in repo settings + DNS
- **HTTPS enforced** — automatic

---

## 8. Build Optimization

> **Build config owned by build tools** — this skill references them.

| Tool    | Skill     | Key Optimizations                                                        |
| ------- | --------- | ------------------------------------------------------------------------ |
| Vite    | `vite`    | `minify: 'esbuild'`, `cssCodeSplit`, `manualChunks`, `assetsInlineLimit` |
| esbuild | `esbuild` | `target: 'es2022'`, `splitting`, `treeShaking`, `minify`                 |
| Astro   | `astro`   | `compressHTML`, `imageService`, `prefetch`                               |

### Universal rules

- **`target: 'es2022'`** — modern browsers, smaller bundles
- **Code splitting** — vendor chunks + route lazy loading
- **Asset hashing** — automatic via Vite/esbuild/Astro
- **Compression** — gzip/brotli at platform level (enable in dashboard)

---

## 9. Environment Variables

> **Env conventions owned by `package-manager`, `vite`, `astro`** — this skill defines platform mapping.

### Platform mapping

| Local (`.env`)     | Vercel                      | Netlify                     | GitHub Pages                |
| ------------------ | --------------------------- | --------------------------- | --------------------------- |
| `VITE_API_URL`     | `VITE_API_URL`              | `VITE_API_URL`              | `VITE_API_URL` (build-time) |
| `SECRET_DB_URL`    | `SECRET_DB_URL` (encrypted) | `SECRET_DB_URL` (encrypted) | ❌ Not supported            |
| `PUBLIC_*` (Astro) | `PUBLIC_*`                  | `PUBLIC_*`                  | `PUBLIC_*` (build-time)     |

### Rules Environment Variables

- **Never commit `.env`** — `.gitignore` includes `.env*`
- **`.env.example` committed** — template for contributors
- **Platform dashboard** — set production vars there
- **Preview deploys** — inherit production vars, override per PR if needed
- **Build-time vs runtime** — Astro `PUBLIC_`/Vite `VITE_` = build-time; secrets = runtime (SSR only)

---

## 10. CI/CD

> **CI owned by `linting`, `package-manager`** — this skill
> defines deploy pipeline.

### Minimal deploy workflow (`.github/workflows/deploy.yml`)

```yaml
name: Deploy

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

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"
      - run: pnpm install --frozen-lockfile
      - run: pnpm typecheck
      - run: pnpm lint
      - run: pnpm format:check
      - run: pnpm test
      - run: pnpm build

  deploy-preview:
    needs: check
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Platform-specific preview deploy (Vercel/Netlify action)
      - uses: amondnet/vercel-action@v25
        if: vars.VERCEL_TOKEN != ''
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: "--scope=${{ secrets.VERCEL_ORG_ID }}"

  deploy-production:
    needs: check
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Platform-specific production deploy
      - uses: amondnet/vercel-action@v25
        if: vars.VERCEL_TOKEN != ''
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: "--prod --scope=${{ secrets.VERCEL_ORG_ID }}"
```

### Rules CI/CD

- **Checks first** — typecheck, lint, format, test, build MUST pass
- **Preview on PR** — automatic preview URL for review
- **Production on main push** — only after checks pass
- **Secrets in GitHub** — `VERCEL_TOKEN`, `NETLIFY_AUTH_TOKEN`, etc.

---

## 11. Pre-Deploy Checklist

- [ ] `pnpm typecheck` passes
- [ ] `pnpm lint` passes (zero errors)
- [ ] `pnpm format:check` passes
- [ ] `pnpm test` passes
- [ ] `pnpm build` succeeds locally
- [ ] Env vars set in platform dashboard
- [ ] Custom domain configured (if applicable)
- [ ] Preview deploy verified on PR
- [ ] CHANGELOG updated for release
- [ ] Git tag created for release

---

## 12. Rollback

### Git-based (all platforms)

```bash
# Tag current production
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0

# Rollback to previous tag
git tag -d v1.2.0
git push origin :refs/tags/v1.2.0
git push origin v1.1.0:main --force-with-lease
```

### Platform-based

| Platform     | Command                                         |
| ------------ | ----------------------------------------------- |
| Vercel       | `vercel rollback <deployment-url>` or dashboard |
| Netlify      | `netlify rollback <deploy-id>` or dashboard     |
| GitHub Pages | Re-deploy previous workflow run                 |
| Cloudflare   | Dashboard → Deployments → Rollback              |

### Rules Rollback

- **Git tag every release** — enables git-based rollback
- **Platform rollback** — faster, preserves git history
- **Force-push main only with lease** — `git push --force-with-lease`

---

## 13. Methodology

Before using ANY deploy config/pattern not documented in this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` + `context7_query-docs` for Vercel, Netlify, GitHub Actions, etc.
2. **Official docs**: vercel.com/docs, netlify.com/docs, GitHub Actions — verify current config.
3. **Project config**: `vercel.json`, `netlify.toml`, `.github/workflows/*.yml` — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.

---

## 14. Prohibitions

- ❌ Do not commit `.env` or secrets — use platform dashboard
- ❌ Do not deploy without passing checks (typecheck, lint, test, build)
- ❌ Do not use different build commands locally vs CI
- ❌ Do not skip preview deploy on PR
- ❌ Do not hardcode platform URLs in code — use env vars
- ❌ Do not disable HTTPS — all platforms enforce it
- ❌ Do not deploy `node_modules` — build output only
- ❌ Do not skip security headers — configure in platform config

---

## 15. References

> **Note:** For Git conventions, see [Git](../git/SKILL.md)
> **Note:** For package manager conventions, see [Package Manager](../package-manager/SKILL.md)
> **Note:** For Vite build config, see [Vite](../vite/SKILL.md)
> **Note:** For esbuild config, see [esbuild](../esbuild/SKILL.md)
> **Note:** For Astro config, see [Astro](../astro/SKILL.md)
> **Note:** For Linting/CI, see [Linting](../linting/SKILL.md)

---

Last updated: 2026-08

