Claude Code Plugins

Community-maintained marketplace

Feedback

deployment-guide

@johunsang/kreatsaas
1
0

SaaS 배포 완벽 가이드 - Vercel, Railway, AWS, Docker

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name deployment-guide
description SaaS 배포 완벽 가이드 - Vercel, Railway, AWS, Docker
triggers deploy, 배포, vercel, railway, docker

SaaS 배포 완벽 가이드

SaaS 프로젝트를 프로덕션에 배포하는 완벽 가이드


1. 배포 전 체크리스트

┌─────────────────────────────────────────────────────────────┐
│ 📋 배포 전 체크리스트                                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│ 🔧 코드 준비                                                  │
│ □ 모든 테스트 통과                                           │
│ □ 린트/타입 에러 없음                                        │
│ □ console.log 제거                                           │
│ □ 하드코딩된 URL 제거                                        │
│ □ 환경변수 분리 완료                                         │
│                                                              │
│ 🔒 보안                                                       │
│ □ API 키 환경변수 처리                                       │
│ □ CORS 설정                                                  │
│ □ Rate Limiting 적용                                         │
│ □ SQL Injection 방지                                         │
│ □ XSS 방지                                                   │
│                                                              │
│ 📊 성능                                                       │
│ □ 이미지 최적화                                              │
│ □ 번들 크기 확인                                             │
│ □ Lighthouse 점수 80+                                        │
│                                                              │
│ 📝 문서                                                       │
│ □ README 작성                                                │
│ □ 환경변수 문서화                                            │
│ □ API 문서화                                                 │
│                                                              │
└─────────────────────────────────────────────────────────────┘

2. Vercel 배포 (권장)

2.1 기본 배포

# 1. Vercel CLI 설치
npm i -g vercel

# 2. 프로젝트 연결 및 배포
vercel

# 3. 프로덕션 배포
vercel --prod

2.2 환경변수 설정

# CLI로 환경변수 추가
vercel env add DATABASE_URL production
vercel env add NEXTAUTH_SECRET production
vercel env add STRIPE_SECRET_KEY production

# 또는 Vercel 대시보드에서:
# Project Settings → Environment Variables

2.3 vercel.json 설정

{
  "framework": "nextjs",
  "buildCommand": "prisma generate && next build",
  "installCommand": "npm install",
  "regions": ["icn1"],
  "functions": {
    "src/app/api/**/*.ts": {
      "maxDuration": 30
    }
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Credentials", "value": "true" },
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Access-Control-Allow-Methods", "value": "GET,POST,PUT,DELETE,OPTIONS" }
      ]
    }
  ],
  "rewrites": [
    { "source": "/sitemap.xml", "destination": "/api/sitemap" }
  ]
}

2.4 데이터베이스 연결 (Vercel Postgres)

# Vercel Postgres 추가
vercel link
vercel storage add

# 환경변수 자동 설정됨
# POSTGRES_URL, POSTGRES_PRISMA_URL 등

2.5 도메인 연결

# 도메인 추가
vercel domains add mysaas.com

# DNS 설정 (도메인 제공업체에서)
# A 레코드: 76.76.21.21
# CNAME: cname.vercel-dns.com

2.6 자동 배포 설정

# .github/workflows/deploy.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Vercel CLI
        run: npm install -g vercel

      - name: Pull Vercel Environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build Project
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

3. Railway 배포

3.1 기본 배포

# 1. Railway CLI 설치
npm install -g @railway/cli

# 2. 로그인
railway login

# 3. 프로젝트 초기화
railway init

# 4. 배포
railway up

3.2 railway.json 설정

{
  "$schema": "https://railway.app/railway.schema.json",
  "build": {
    "builder": "NIXPACKS",
    "buildCommand": "npm run build"
  },
  "deploy": {
    "startCommand": "npm run start",
    "healthcheckPath": "/api/health",
    "healthcheckTimeout": 100,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 10
  }
}

3.3 데이터베이스 추가

# PostgreSQL 추가
railway add -p postgresql

# 환경변수 자동 설정됨
# DATABASE_URL 등

3.4 도메인 설정

# 커스텀 도메인
railway domain add mysaas.com

# 또는 Railway 대시보드에서:
# Settings → Domains → Add Custom Domain

4. AWS 배포

4.1 AWS Amplify (가장 쉬움)

# 1. Amplify CLI 설치
npm install -g @aws-amplify/cli

# 2. 초기화
amplify init

# 3. 호스팅 추가
amplify add hosting

# 4. 배포
amplify publish

4.2 amplify.yml

version: 1
frontend:
  phases:
    preBuild:
      commands:
        - npm ci
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: .next
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
      - .next/cache/**/*

4.3 AWS EC2 + Docker

# EC2 인스턴스에서
# 1. Docker 설치
sudo yum update -y
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker

# 2. 이미지 빌드 및 실행
docker build -t mysaas .
docker run -d -p 80:3000 --env-file .env.production mysaas

4.4 AWS ECS + Fargate

# task-definition.json
{
  "family": "mysaas",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "mysaas",
      "image": "123456789.dkr.ecr.ap-northeast-2.amazonaws.com/mysaas:latest",
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        { "name": "NODE_ENV", "value": "production" }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:ap-northeast-2:123456789:secret:mysaas/db"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/mysaas",
          "awslogs-region": "ap-northeast-2",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512"
}

5. Docker 배포

5.1 Dockerfile

# Dockerfile
FROM node:20-alpine AS base

# 의존성 설치
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# 빌드
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN npx prisma generate
RUN npm run build

# 실행
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

5.2 docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mysaas
      - NEXTAUTH_URL=https://mysaas.com
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mysaas
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./certs:/etc/nginx/certs
    depends_on:
      - app
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

5.3 nginx.conf

events {
    worker_connections 1024;
}

http {
    upstream app {
        server app:3000;
    }

    server {
        listen 80;
        server_name mysaas.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name mysaas.com;

        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;

        # 보안 헤더
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;

        # Gzip
        gzip on;
        gzip_types text/plain text/css application/json application/javascript;

        location / {
            proxy_pass http://app;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # 정적 파일 캐싱
        location /_next/static {
            proxy_pass http://app;
            add_header Cache-Control "public, max-age=31536000, immutable";
        }
    }
}

6. CI/CD 파이프라인

6.1 GitHub Actions

# .github/workflows/ci-cd.yml
name: CI/CD

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 테스트
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        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 type check
        run: npm run type-check

      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test

  # 빌드
  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

  # 배포
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /app/mysaas
            docker compose pull
            docker compose up -d
            docker system prune -f

6.2 배포 스크립트

#!/bin/bash
# scripts/deploy.sh

set -e

echo "🚀 Starting deployment..."

# 변수
DOCKER_IMAGE="ghcr.io/your-username/mysaas:latest"
COMPOSE_FILE="docker-compose.prod.yml"

# 이미지 풀
echo "📦 Pulling latest image..."
docker pull $DOCKER_IMAGE

# 서비스 재시작
echo "🔄 Restarting services..."
docker compose -f $COMPOSE_FILE up -d --force-recreate

# 헬스 체크
echo "🏥 Health check..."
sleep 10
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/health)

if [ $HEALTH -eq 200 ]; then
  echo "✅ Deployment successful!"
else
  echo "❌ Health check failed! Rolling back..."
  docker compose -f $COMPOSE_FILE rollback
  exit 1
fi

# 정리
echo "🧹 Cleaning up..."
docker system prune -f

echo "🎉 Done!"

7. 모니터링 설정

7.1 Sentry 에러 추적

npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
  replaysOnErrorSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
});

7.2 Uptime 모니터링

// src/app/api/health/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';

export async function GET() {
  try {
    // DB 연결 확인
    await prisma.$queryRaw`SELECT 1`;

    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      services: {
        database: 'connected',
        cache: 'connected',
      },
    });
  } catch (error) {
    return NextResponse.json(
      { status: 'unhealthy', error: String(error) },
      { status: 500 }
    );
  }
}

7.3 로깅 (Axiom/Logtail)

npm install @axiomhq/winston
// src/lib/logger.ts
import winston from 'winston';
import { WinstonTransport as AxiomTransport } from '@axiomhq/winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new AxiomTransport({
      dataset: process.env.AXIOM_DATASET!,
      token: process.env.AXIOM_TOKEN!,
    }),
    new winston.transports.Console({
      format: winston.format.simple(),
    }),
  ],
});

export default logger;

8. 환경변수 관리

8.1 환경변수 템플릿

# .env.example

# App
NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://mysaas.com

# Database
DATABASE_URL=postgresql://user:password@host:5432/db?schema=public

# Auth
NEXTAUTH_URL=https://mysaas.com
NEXTAUTH_SECRET=your-secret-key-at-least-32-chars

# OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# Payment
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx

# Storage
CLOUDFLARE_R2_ACCESS_KEY=
CLOUDFLARE_R2_SECRET_KEY=
CLOUDFLARE_R2_BUCKET=

# AI
OPENAI_API_KEY=

# Monitoring
SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=

# Analytics
NEXT_PUBLIC_GA_ID=G-XXXXXX

8.2 환경변수 검증

// src/lib/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  OPENAI_API_KEY: z.string().startsWith('sk-'),
});

export function validateEnv() {
  const result = envSchema.safeParse(process.env);

  if (!result.success) {
    console.error('❌ Invalid environment variables:');
    console.error(result.error.format());
    throw new Error('Invalid environment variables');
  }

  return result.data;
}

// 앱 시작 시 검증
validateEnv();

9. 배포 플랫폼 비교

┌────────────────────────────────────────────────────────────────────────┐
│ 📊 배포 플랫폼 비교                                                      │
├────────────┬──────────┬──────────┬──────────┬──────────────────────────┤
│ 플랫폼     │ 난이도    │ 가격     │ 특징     │ 추천 대상               │
├────────────┼──────────┼──────────┼──────────┼──────────────────────────┤
│ Vercel     │ ⭐⭐⭐⭐⭐ │ 무료~$20 │ Next.js  │ 초보자, 프론트엔드 중심  │
│            │          │          │ 최적화   │                          │
├────────────┼──────────┼──────────┼──────────┼──────────────────────────┤
│ Railway    │ ⭐⭐⭐⭐   │ $5~     │ 간편     │ 풀스택, DB 포함         │
│            │          │          │ 올인원   │                          │
├────────────┼──────────┼──────────┼──────────┼──────────────────────────┤
│ AWS        │ ⭐⭐       │ 사용량  │ 유연성   │ 대규모, 엔터프라이즈    │
│            │          │          │ 최고     │                          │
├────────────┼──────────┼──────────┼──────────┼──────────────────────────┤
│ Docker +   │ ⭐⭐       │ VPS비용 │ 완전한   │ 숙련자, 커스텀 필요     │
│ VPS        │          │          │ 제어     │                          │
├────────────┼──────────┼──────────┼──────────┼──────────────────────────┤
│ Cloudflare │ ⭐⭐⭐     │ 무료~   │ Edge,    │ 글로벌, 정적 중심       │
│ Pages      │          │          │ 빠름     │                          │
└────────────┴──────────┴──────────┴──────────┴──────────────────────────┘

10. 배포 후 체크리스트

┌─────────────────────────────────────────────────────────────┐
│ ✅ 배포 후 체크리스트                                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│ 🌐 접근성                                                     │
│ □ 메인 페이지 로딩 확인                                      │
│ □ 모든 라우트 접근 가능                                      │
│ □ API 엔드포인트 동작                                        │
│ □ 모바일 반응형 확인                                         │
│                                                              │
│ 🔒 보안                                                       │
│ □ HTTPS 적용 확인                                            │
│ □ 환경변수 노출 없음                                         │
│ □ 보안 헤더 설정                                             │
│                                                              │
│ 📊 성능                                                       │
│ □ Lighthouse 점수 확인                                       │
│ □ Core Web Vitals 통과                                       │
│ □ 첫 페이지 로드 3초 이내                                    │
│                                                              │
│ 🔍 SEO                                                        │
│ □ sitemap.xml 생성 확인                                      │
│ □ robots.txt 확인                                            │
│ □ Open Graph 이미지 확인                                     │
│ □ Google Search Console 제출                                 │
│                                                              │
│ 📈 모니터링                                                   │
│ □ 에러 추적 연동 (Sentry)                                    │
│ □ 업타임 모니터링 설정                                       │
│ □ 로깅 설정                                                  │
│ □ Analytics 연동                                             │
│                                                              │
│ 💳 결제 (해당 시)                                             │
│ □ Stripe 웹훅 연결 확인                                      │
│ □ 테스트 결제 성공                                           │
│ □ 이메일 알림 동작                                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘