Use Cases

Claude Code × GCP Cloud Run 완전 가이드 | 서버리스 컨테이너 자동 배포

Claude Code로 GCP Cloud Run 배포를 빠르게. Dockerfile 생성, 자동 스케일링, CI/CD 파이프라인, Secret Manager 연동까지 실제 코드로 완전 해설.

“GCP에서 컨테이너를 실행하고 싶은데 설정이 너무 많아서 어디서 시작해야 할지 모르겠다” — 저도 예전에 그렇게 느꼈습니다. 그런데 Cloud Run을 실제로 사용해보니 ECS에 비해 설정이 훨씬 간단해서 놀랐습니다. VPC 설정 없이, 태스크 롤 설계 없이, 클러스터 관리 없이. 컨테이너 이미지만 준비하면 거의 즉시 HTTPS 엔드포인트가 생성됩니다.

Claude Code와 조합하면 Dockerfile 생성부터 Cloud Build CI/CD 파이프라인 구축까지 놀랍도록 짧은 시간에 완료됩니다. 이 글에서는 제가 실제로 시도한 단계를 6단계로 설명합니다.


Cloud Run이 ECS보다 간단한 이유

Cloud Run은 GCP의 완전 관리형 컨테이너 실행 서비스입니다. HTTP 요청이 들어올 때만 컨테이너가 시작되는 “요청 기반 서버리스” 모델을 사용합니다.

ECS와의 비교:

Cloud Run:
- 클러스터 관리: 불필요
- 네트워크 설정: 기본적으로 외부 HTTPS 공개
- 스케일링:     0대~N대 완전 자동
- 요금:         요청 처리 시간 기반 (유휴 시 거의 무료)

AWS ECS/Fargate:
- 클러스터 관리: 필요 (Cluster / Service / TaskDef 3계층)
- 네트워크 설정: VPC 및 보안 그룹 설계 필요
- 스케일링:     최소 인스턴스 수 요금 발생
- 요금:         실행 중인 태스크 시간 기반

Step 1: Dockerfile 자동 생성

claude -p "
다음 조건으로 프로덕션 수준의 Dockerfile을 생성해줘.

- 앱: TypeScript + Express API (포트 8080)
- Node.js 버전: 22 LTS
- 멀티스테이지 빌드 (builder → runner)
- 비루트 사용자 (node 사용자)로 실행
- .dockerignore도 함께 생성
- Cloud Run 권장 PORT 환경변수 대응
- 헬스체크: /health 엔드포인트
"

생성된 Dockerfile:

# ---- builder 스테이지 ----
FROM node:22-alpine AS builder
WORKDIR /app

# 빌드 캐시 활용을 위해 먼저 의존성 복사
COPY package*.json ./
RUN npm ci --only=production=false

COPY tsconfig.json ./
COPY src ./src
RUN npm run build

# ---- runner 스테이지 ----
FROM node:22-alpine AS runner
WORKDIR /app

# 보안: 비루트 사용자로 실행
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 프로덕션 의존성만 설치
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 빌드 결과물 복사
COPY --from=builder /app/dist ./dist

# Cloud Run은 PORT 환경변수로 포트를 전달
ENV PORT=8080
ENV NODE_ENV=production

# 비루트 사용자로 전환
USER appuser

EXPOSE 8080
CMD ["node", "dist/index.js"]

Step 2: Cloud Run 최초 배포

gcloud run deploy myapp-api \
  --image asia-northeast1-docker.pkg.dev/my-project-123/myapp/api:v1.0.0 \
  --region asia-northeast1 \
  --platform managed \
  --memory 512Mi \
  --cpu 1 \
  --concurrency 80 \
  --allow-unauthenticated \
  --set-env-vars NODE_ENV=production \
  --port 8080

Step 3: 자동 스케일링 설정

gcloud run services update myapp-api \
  --region asia-northeast1 \
  --min-instances 1 \
  --max-instances 20 \
  --concurrency 80 \
  --cpu-throttling \
  --execution-environment gen2
# service.yaml - Cloud Run 서비스 설정 파일
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: myapp-api
spec:
  template:
    metadata:
      annotations:
        # 최소 인스턴스 수 (콜드 스타트 대책)
        autoscaling.knative.dev/minScale: "1"
        # 최대 인스턴스 수 (비용 상한)
        autoscaling.knative.dev/maxScale: "20"
        # CPU 사용률 70%에서 스케일아웃
        autoscaling.knative.dev/target-utilization-percentage: "70"
        run.googleapis.com/execution-environment: gen2
    spec:
      containerConcurrency: 80
      containers:
        - image: asia-northeast1-docker.pkg.dev/my-project-123/myapp/api:latest
          resources:
            limits:
              memory: 512Mi
              cpu: "1"

Step 4: Secret Manager 연동

# 시크릿 등록
echo -n "postgresql://user:password@host:5432/db" | \
  gcloud secrets create DATABASE_URL --data-file=-

# 서비스 계정에 읽기 권한 부여
gcloud projects add-iam-policy-binding my-project-123 \
  --member="serviceAccount:[email protected]" \
  --role="roles/secretmanager.secretAccessor"

# Cloud Run에 시크릿 마운트
gcloud run services update myapp-api \
  --region asia-northeast1 \
  --set-secrets="DATABASE_URL=DATABASE_URL:latest,SENDGRID_API_KEY=SENDGRID_API_KEY:latest,JWT_SECRET=JWT_SECRET:latest"
// src/config.ts
export const config = {
  databaseUrl: process.env.DATABASE_URL!,
  sendgridApiKey: process.env.SENDGRID_API_KEY!,
  jwtSecret: process.env.JWT_SECRET!,
  port: parseInt(process.env.PORT || "8080", 10),
  nodeEnv: process.env.NODE_ENV || "development",
};

// 시작 시 필수 시크릿 검증
const requiredEnvVars = ["DATABASE_URL", "SENDGRID_API_KEY", "JWT_SECRET"];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    console.error(`Missing required environment variable: ${envVar}`);
    process.exit(1);
  }
}

Step 5: Cloud Build CI/CD 파이프라인 구축

# cloudbuild.yaml
steps:
  # Step 1: 의존성 설치 및 테스트 실행
  - name: "node:22-alpine"
    id: "test"
    entrypoint: "sh"
    args:
      - "-c"
      - |
        npm ci
        npm run test
        npm run lint

  # Step 2: Docker 이미지 빌드
  - name: "gcr.io/cloud-builders/docker"
    id: "build"
    args:
      - "build"
      - "-t"
      - "asia-northeast1-docker.pkg.dev/$PROJECT_ID/myapp/api:$COMMIT_SHA"
      - "-t"
      - "asia-northeast1-docker.pkg.dev/$PROJECT_ID/myapp/api:latest"
      - "."

  # Step 3: Artifact Registry에 푸시
  - name: "gcr.io/cloud-builders/docker"
    id: "push"
    args:
      - "push"
      - "--all-tags"
      - "asia-northeast1-docker.pkg.dev/$PROJECT_ID/myapp/api"

  # Step 4: Cloud Run에 배포
  - name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
    id: "deploy"
    entrypoint: "gcloud"
    args:
      - "run"
      - "deploy"
      - "myapp-api"
      - "--image"
      - "asia-northeast1-docker.pkg.dev/$PROJECT_ID/myapp/api:$COMMIT_SHA"
      - "--region"
      - "asia-northeast1"
      - "--platform"
      - "managed"
      - "--quiet"

  # Step 5: Slack 알림 (성공 시)
  - name: "curlimages/curl"
    id: "notify-success"
    entrypoint: "curl"
    args:
      - "-X"
      - "POST"
      - "-H"
      - "Content-type: application/json"
      - "--data"
      - '{"text":"✅ Cloud Run 배포 완료: $COMMIT_SHA"}'
      - "$_SLACK_WEBHOOK_URL"

options:
  logging: CLOUD_LOGGING_ONLY
  machineType: E2_HIGHCPU_8
timeout: "1200s"

Step 6: 커스텀 도메인 설정과 로드 밸런서

# main.tf - Cloud Run + Load Balancer + SSL
resource "google_compute_region_network_endpoint_group" "cloudrun_neg" {
  name                  = "myapp-neg"
  network_endpoint_type = "SERVERLESS"
  region                = "asia-northeast1"
  cloud_run {
    service = "myapp-api"
  }
}

resource "google_compute_managed_ssl_certificate" "default" {
  name = "myapp-ssl-cert"
  managed {
    domains = ["api.example.com"]
  }
}

resource "google_compute_security_policy" "policy" {
  name = "myapp-security-policy"
  rule {
    action   = "deny(403)"
    priority = "1000"
    match {
      expr {
        expression = "evaluatePreconfiguredExpr('sqli-stable')"
      }
    }
    description = "SQL 인젝션 대책"
  }
  rule {
    action   = "allow"
    priority = "2147483647"
    match {
      versioned_expr = "SRC_IPS_V1"
      config { src_ip_ranges = ["*"] }
    }
    description = "기본 허용"
  }
}

함정 5선

1. 콜드 스타트로 인한 타임아웃

프로덕션에서 min-instances를 1 이상으로 설정하세요.

gcloud run services update myapp-api --min-instances 1 --region asia-northeast1

2. SIGTERM 처리를 잊어버리는 경우

// src/index.ts - SIGTERM 적절히 처리
process.on("SIGTERM", () => {
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 30000);
});

3. 환경변수에 기밀 정보를 직접 작성하는 경우

# ❌ 절대 NG: 시크릿을 평문으로 작성
gcloud run services update myapp-api --set-env-vars DATABASE_PASSWORD=mypassword123

# ✅ 정답: Secret Manager 경유로 안전하게 전달
gcloud run services update myapp-api --set-secrets="DATABASE_PASSWORD=DATABASE_PASSWORD:latest"

4. 메모리 설정 부족

# 메모리 부족 대책: Node.js 힙 사이즈를 명시
CMD ["node", "--max-old-space-size=384", "dist/index.js"]

5. 요청 외 백그라운드 처리를 하는 경우

# 항상 CPU를 할당하는 모드 (백그라운드 처리가 필요한 경우)
gcloud run services update myapp-api --no-cpu-throttling --region asia-northeast1

정리

태스크Claude Code의 기여
Dockerfile 생성멀티스테이지 빌드·비루트 설정 자동화
최초 배포요건에서 gcloud 커맨드 일식 생성
스케일링 설정min/max 인스턴스·CPU 임계값 최적화
Secret Manager 연동시크릿 등록·권한 부여·마운트 설정 생성
CI/CD 파이프라인테스트 포함 cloudbuild.yaml 생성
커스텀 도메인Terraform 로드 밸런서 구성 자동 생성

관련 글

참고 자료

#claude-code #gcp #cloud-run #docker #typescript #serverless
무료 제공

무료 PDF: 5분 완성 Claude Code 치트시트

이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.

개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.

Claude Code 워크플로우를 한 단계 업그레이드하세요

지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.

Masa

이 글을 작성한 사람

Masa

Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.