Claude Skill

deployment-guide

Vercel frontend + Railway/Fly.io backend deployment, env vars, health checks. Use when deploying a Next.js frontend to Vercel or a NestJS backend to Railway or Fly.io, or troubleshooting a deployment issue.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download sabahattink-antigravity-fullstack-hq-skills_deployment-guide-1acbfa7.zip · 3 KB
Part of sabahattink/antigravity-fullstack-hq — 27 skills

Install

skills CLI npx skills add https://github.com/sabahattink/antigravity-fullstack-hq/tree/main/skills/deployment-guide
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install sabahattink-antigravity-fullstack-hq@llmmart
Git git clone https://github.com/sabahattink/antigravity-fullstack-hq.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole sabahattink/antigravity-fullstack-hq collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Deployment Guide

Frontend — Vercel

Setup

# Install Vercel CLI
npm i -g vercel

# Login
vercel login

# Link project (run once in the project root)
vercel link

# Deploy to preview
vercel

# Deploy to production
vercel --prod

vercel.json

{
  "framework": "nextjs",
  "buildCommand": "pnpm run build",
  "installCommand": "pnpm install --frozen-lockfile",
  "outputDirectory": ".next",
  "regions": ["iad1"],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options",        "value": "DENY" },
        { "key": "X-XSS-Protection",       "value": "1; mode=block" },
        { "key": "Referrer-Policy",        "value": "strict-origin-when-cross-origin" }
      ]
    },
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "no-store, max-age=0" }
      ]
    },
    {
      "source": "/_next/static/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ],
  "rewrites": [
    {
      "source": "/api/:path*",
      "destination": "https://api.example.com/api/:path*"
    }
  ]
}

Vercel Environment Variables

# Set env vars per environment
vercel env add NEXT_PUBLIC_API_URL production
vercel env add NEXT_PUBLIC_API_URL preview
vercel env add NEXT_PUBLIC_API_URL development

# Pull to local .env.local
vercel env pull .env.local

next.config.ts for Production

import type { NextConfig } from 'next'

const config: NextConfig = {
  output: 'standalone',

  // Strict mode catches side effects in development
  reactStrictMode: true,

  // Image optimization
  images: {
    domains: ['cdn.example.com', 'avatars.githubusercontent.com'],
    formats: ['image/avif', 'image/webp'],
  },

  // Bundle analyzer (install @next/bundle-analyzer)
  // ...(process.env.ANALYZE === 'true' && require('@next/bundle-analyzer')())

  // Security headers
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-DNS-Prefetch-Control', value: 'on' },
        ],
      },
    ]
  },

  // Redirects
  async redirects() {
    return [
      { source: '/old-path', destination: '/new-path', permanent: true },
    ]
  },
}

export default config

Backend — Railway

Setup

# Install Railway CLI
npm i -g @railway/cli

# Login
railway login

# Initialize (run in project root)
railway init

# Deploy
railway up

# Open dashboard
railway open

# View logs
railway logs

# Run commands in service
railway run pnpm run migration:run

railway.json

{
  "$schema": "https://railway.app/railway.schema.json",
  "build": {
    "builder":      "DOCKERFILE",
    "dockerfilePath": "apps/api/Dockerfile"
  },
  "deploy": {
    "startCommand": "node dist/main",
    "healthcheckPath": "/health/liveness",
    "healthcheckTimeout": 30,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 3
  }
}

Required Environment Variables (Railway)

# Set via Railway dashboard or CLI
DATABASE_URL    = ${{Postgres.DATABASE_URL}}   # Railway managed Postgres
REDIS_URL       = ${{Redis.REDIS_URL}}          # Railway managed Redis
NODE_ENV        = production
PORT            = ${{PORT}}                     # Railway injects this
JWT_SECRET      = <generate: openssl rand -base64 32>
CORS_ORIGINS    = https://your-app.vercel.app
LOG_LEVEL       = info

Backend — Fly.io (Alternative)

Setup

# Install flyctl
curl -L https://fly.io/install.sh | sh

# Login
fly auth login

# Launch app (creates fly.toml)
fly launch --name my-api --region iad --no-deploy

# Deploy
fly deploy

# Scale
fly scale count 2
fly scale memory 512

# View logs
fly logs

# SSH into instance
fly ssh console

fly.toml

app      = "my-api"
primary_region = "iad"

[build]
  dockerfile = "apps/api/Dockerfile"

[env]
  NODE_ENV  = "production"
  PORT      = "8080"

[http_service]
  internal_port = 8080
  force_https   = true
  auto_stop_machines  = true
  auto_start_machines = true
  min_machines_running = 1

  [http_service.concurrency]
    type       = "connections"
    hard_limit = 200
    soft_limit = 150

[[vm]]
  memory   = "512mb"
  cpu_kind = "shared"
  cpus     = 1

[checks]
  [checks.health]
    grace_period  = "30s"
    interval      = "15s"
    method        = "GET"
    path          = "/health/liveness"
    port          = 8080
    timeout       = "5s"
    type          = "http"

Health Check Endpoints

// health/health.controller.ts — must respond before traffic is routed

@Controller('health')
export class HealthController {
  constructor(
    private health:  HealthCheckService,
    private db:      TypeOrmHealthIndicator,
  ) {}

  // Liveness: is the process running?
  @Get('liveness')
  liveness() {
    return { status: 'ok', timestamp: new Date().toISOString() }
  }

  // Readiness: can we serve traffic?
  @Get('readiness')
  @HealthCheck()
  readiness() {
    return this.health.check([
      () => this.db.pingCheck('database', { timeout: 3000 }),
    ])
  }

  // Full check: all dependencies
  @Get()
  @HealthCheck()
  full() {
    return this.health.check([
      () => this.db.pingCheck('database'),
    ])
  }
}

Database Migrations on Deploy

// src/cli.ts — run as a separate Railway service or pre-deploy command
import { NestFactory } from '@nestjs/core'
import { MigrationModule } from './migration/migration.module'

async function runMigrations() {
  const app = await NestFactory.createApplicationContext(MigrationModule)
  const dataSource = app.get(DataSource)

  console.log('Running migrations...')
  const migrations = await dataSource.runMigrations()
  console.log(`Ran ${migrations.length} migrations`)

  await app.close()
  process.exit(0)
}

runMigrations().catch(err => {
  console.error('Migration failed:', err)
  process.exit(1)
})
// package.json
{
  "scripts": {
    "migration:run": "ts-node src/cli.ts",
    "start:prod": "node dist/main"
  }
}

Zero-Downtime Deployments

# Railway: automatic rolling deployments (no config needed)
# Fly.io: rolling deployments with health checks

# Ensure your app handles SIGTERM gracefully
// main.ts — graceful shutdown
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.enableShutdownHooks()

  await app.listen(process.env.PORT ?? 3000)

  // Log when server is ready
  console.log(`API running on port ${process.env.PORT ?? 3000}`)
}

Environment Checklist

Production Environment Variables Checklist:
□ NODE_ENV=production
□ DATABASE_URL (managed service with connection pooling)
□ REDIS_URL (if using caching/sessions)
□ JWT_SECRET (min 32 chars, random)
□ CORS_ORIGINS (exact frontend domains, no wildcards)
□ LOG_LEVEL=info (not debug)
□ PORT (injected by platform)

Frontend:
□ NEXT_PUBLIC_API_URL (production backend URL)
□ No secrets in NEXT_PUBLIC_* vars (they're exposed to browser)

Forbidden Patterns

  • Never deploy with NODE_ENV=development in production
  • Never use the same JWT secret across environments
  • Never skip health check endpoints — platforms use them for traffic routing
  • Never run database migrations during app startup — run them as a separate step
  • Never put NEXT_PUBLIC_* values that are secrets — they're bundled into the browser
  • Never deploy without verifying the build passes CI first
  • Never use 0.0.0.0 as CORS_ORIGINS in production — specify exact origins
Files (antigravity-fullstack-hq)
  • SKILL.md 7.7 KB
    ---
    name: deployment-guide
    description: Vercel frontend + Railway/Fly.io backend deployment, env vars, health checks. Use when deploying a Next.js frontend to Vercel or a NestJS backend to Railway or Fly.io, or troubleshooting a deployment issue.
    ---
    
    # Deployment Guide
    
    ## Frontend — Vercel
    
    ### Setup
    ```bash
    # Install Vercel CLI
    npm i -g vercel
    
    # Login
    vercel login
    
    # Link project (run once in the project root)
    vercel link
    
    # Deploy to preview
    vercel
    
    # Deploy to production
    vercel --prod
    ```
    
    ### vercel.json
    ```json
    {
      "framework": "nextjs",
      "buildCommand": "pnpm run build",
      "installCommand": "pnpm install --frozen-lockfile",
      "outputDirectory": ".next",
      "regions": ["iad1"],
      "headers": [
        {
          "source": "/(.*)",
          "headers": [
            { "key": "X-Content-Type-Options", "value": "nosniff" },
            { "key": "X-Frame-Options",        "value": "DENY" },
            { "key": "X-XSS-Protection",       "value": "1; mode=block" },
            { "key": "Referrer-Policy",        "value": "strict-origin-when-cross-origin" }
          ]
        },
        {
          "source": "/api/(.*)",
          "headers": [
            { "key": "Cache-Control", "value": "no-store, max-age=0" }
          ]
        },
        {
          "source": "/_next/static/(.*)",
          "headers": [
            { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
          ]
        }
      ],
      "rewrites": [
        {
          "source": "/api/:path*",
          "destination": "https://api.example.com/api/:path*"
        }
      ]
    }
    ```
    
    ### Vercel Environment Variables
    ```bash
    # Set env vars per environment
    vercel env add NEXT_PUBLIC_API_URL production
    vercel env add NEXT_PUBLIC_API_URL preview
    vercel env add NEXT_PUBLIC_API_URL development
    
    # Pull to local .env.local
    vercel env pull .env.local
    ```
    
    ### next.config.ts for Production
    ```typescript
    import type { NextConfig } from 'next'
    
    const config: NextConfig = {
      output: 'standalone',
    
      // Strict mode catches side effects in development
      reactStrictMode: true,
    
      // Image optimization
      images: {
        domains: ['cdn.example.com', 'avatars.githubusercontent.com'],
        formats: ['image/avif', 'image/webp'],
      },
    
      // Bundle analyzer (install @next/bundle-analyzer)
      // ...(process.env.ANALYZE === 'true' && require('@next/bundle-analyzer')())
    
      // Security headers
      async headers() {
        return [
          {
            source: '/(.*)',
            headers: [
              { key: 'X-DNS-Prefetch-Control', value: 'on' },
            ],
          },
        ]
      },
    
      // Redirects
      async redirects() {
        return [
          { source: '/old-path', destination: '/new-path', permanent: true },
        ]
      },
    }
    
    export default config
    ```
    
    ## Backend — Railway
    
    ### Setup
    ```bash
    # Install Railway CLI
    npm i -g @railway/cli
    
    # Login
    railway login
    
    # Initialize (run in project root)
    railway init
    
    # Deploy
    railway up
    
    # Open dashboard
    railway open
    
    # View logs
    railway logs
    
    # Run commands in service
    railway run pnpm run migration:run
    ```
    
    ### railway.json
    ```json
    {
      "$schema": "https://railway.app/railway.schema.json",
      "build": {
        "builder":      "DOCKERFILE",
        "dockerfilePath": "apps/api/Dockerfile"
      },
      "deploy": {
        "startCommand": "node dist/main",
        "healthcheckPath": "/health/liveness",
        "healthcheckTimeout": 30,
        "restartPolicyType": "ON_FAILURE",
        "restartPolicyMaxRetries": 3
      }
    }
    ```
    
    ### Required Environment Variables (Railway)
    ```bash
    # Set via Railway dashboard or CLI
    DATABASE_URL    = ${{Postgres.DATABASE_URL}}   # Railway managed Postgres
    REDIS_URL       = ${{Redis.REDIS_URL}}          # Railway managed Redis
    NODE_ENV        = production
    PORT            = ${{PORT}}                     # Railway injects this
    JWT_SECRET      = <generate: openssl rand -base64 32>
    CORS_ORIGINS    = https://your-app.vercel.app
    LOG_LEVEL       = info
    ```
    
    ## Backend — Fly.io (Alternative)
    
    ### Setup
    ```bash
    # Install flyctl
    curl -L https://fly.io/install.sh | sh
    
    # Login
    fly auth login
    
    # Launch app (creates fly.toml)
    fly launch --name my-api --region iad --no-deploy
    
    # Deploy
    fly deploy
    
    # Scale
    fly scale count 2
    fly scale memory 512
    
    # View logs
    fly logs
    
    # SSH into instance
    fly ssh console
    ```
    
    ### fly.toml
    ```toml
    app      = "my-api"
    primary_region = "iad"
    
    [build]
      dockerfile = "apps/api/Dockerfile"
    
    [env]
      NODE_ENV  = "production"
      PORT      = "8080"
    
    [http_service]
      internal_port = 8080
      force_https   = true
      auto_stop_machines  = true
      auto_start_machines = true
      min_machines_running = 1
    
      [http_service.concurrency]
        type       = "connections"
        hard_limit = 200
        soft_limit = 150
    
    [[vm]]
      memory   = "512mb"
      cpu_kind = "shared"
      cpus     = 1
    
    [checks]
      [checks.health]
        grace_period  = "30s"
        interval      = "15s"
        method        = "GET"
        path          = "/health/liveness"
        port          = 8080
        timeout       = "5s"
        type          = "http"
    ```
    
    ## Health Check Endpoints
    
    ```typescript
    // health/health.controller.ts — must respond before traffic is routed
    
    @Controller('health')
    export class HealthController {
      constructor(
        private health:  HealthCheckService,
        private db:      TypeOrmHealthIndicator,
      ) {}
    
      // Liveness: is the process running?
      @Get('liveness')
      liveness() {
        return { status: 'ok', timestamp: new Date().toISOString() }
      }
    
      // Readiness: can we serve traffic?
      @Get('readiness')
      @HealthCheck()
      readiness() {
        return this.health.check([
          () => this.db.pingCheck('database', { timeout: 3000 }),
        ])
      }
    
      // Full check: all dependencies
      @Get()
      @HealthCheck()
      full() {
        return this.health.check([
          () => this.db.pingCheck('database'),
        ])
      }
    }
    ```
    
    ## Database Migrations on Deploy
    
    ```typescript
    // src/cli.ts — run as a separate Railway service or pre-deploy command
    import { NestFactory } from '@nestjs/core'
    import { MigrationModule } from './migration/migration.module'
    
    async function runMigrations() {
      const app = await NestFactory.createApplicationContext(MigrationModule)
      const dataSource = app.get(DataSource)
    
      console.log('Running migrations...')
      const migrations = await dataSource.runMigrations()
      console.log(`Ran ${migrations.length} migrations`)
    
      await app.close()
      process.exit(0)
    }
    
    runMigrations().catch(err => {
      console.error('Migration failed:', err)
      process.exit(1)
    })
    ```
    
    ```json
    // package.json
    {
      "scripts": {
        "migration:run": "ts-node src/cli.ts",
        "start:prod": "node dist/main"
      }
    }
    ```
    
    ## Zero-Downtime Deployments
    
    ```bash
    # Railway: automatic rolling deployments (no config needed)
    # Fly.io: rolling deployments with health checks
    
    # Ensure your app handles SIGTERM gracefully
    ```
    
    ```typescript
    // main.ts — graceful shutdown
    async function bootstrap() {
      const app = await NestFactory.create(AppModule)
      app.enableShutdownHooks()
    
      await app.listen(process.env.PORT ?? 3000)
    
      // Log when server is ready
      console.log(`API running on port ${process.env.PORT ?? 3000}`)
    }
    ```
    
    ## Environment Checklist
    
    ```
    Production Environment Variables Checklist:
    □ NODE_ENV=production
    □ DATABASE_URL (managed service with connection pooling)
    □ REDIS_URL (if using caching/sessions)
    □ JWT_SECRET (min 32 chars, random)
    □ CORS_ORIGINS (exact frontend domains, no wildcards)
    □ LOG_LEVEL=info (not debug)
    □ PORT (injected by platform)
    
    Frontend:
    □ NEXT_PUBLIC_API_URL (production backend URL)
    □ No secrets in NEXT_PUBLIC_* vars (they're exposed to browser)
    ```
    
    ## Forbidden Patterns
    
    - Never deploy with `NODE_ENV=development` in production
    - Never use the same JWT secret across environments
    - Never skip health check endpoints — platforms use them for traffic routing
    - Never run database migrations during app startup — run them as a separate step
    - Never put `NEXT_PUBLIC_*` values that are secrets — they're bundled into the browser
    - Never deploy without verifying the build passes CI first
    - Never use `0.0.0.0` as `CORS_ORIGINS` in production — specify exact origins
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related