NestBook / Cloudflare + D1

왜 시크릿 관리가 중요한가

Workers 코드에 API 키를 하드코딩하면 GitHub에 커밋하는 순간 전 세계에 공개된다. Cloudflare Workers는 환경 변수를 코드 바깥에서 주입하는 세 가지 메커니즘을 제공한다.

방식저장 위치암호화용도
wrangler.toml vars파일(코드와 함께)공개해도 무방한 설정값
.dev.vars로컬 파일(gitignore)로컬 개발용 시크릿
wrangler secretCloudflare 서버프로덕션 시크릿

wrangler.toml — 비밀이 아닌 설정값

# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# 환경 변수 (평문 — 커밋해도 무방한 값만)
[vars]
APP_ENV = "production"
LOG_LEVEL = "info"
MAX_UPLOAD_SIZE = "10485760"   # 10 MB

Workers 코드에서는 env.APP_ENV 로 접근한다. wrangler.toml 에 넣은 값은 배포 번들에 포함되므로 Cloudflare Dashboard에서도 평문으로 볼 수 있다.


.dev.vars — 로컬 개발용 시크릿

로컬에서 wrangler dev 실행 시 .dev.vars 파일을 자동으로 읽는다.

# .dev.vars  (반드시 .gitignore에 추가)
DATABASE_URL=sqlite:./local.db
API_SECRET=dev-secret-key-12345
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxx
R2_ACCESS_KEY_ID=test-access-key
R2_SECRET_ACCESS_KEY=test-secret-key
# .gitignore
.dev.vars
.dev.vars.*

.dev.vars 가 없으면 wrangler dev 실행 시 해당 변수는 undefined 가 된다. 팀원에게는 .dev.vars.example 파일(실제 값 없이 키만)을 커밋해 필요한 변수를 안내한다.

# .dev.vars.example  (커밋 O)
DATABASE_URL=
API_SECRET=
STRIPE_SECRET_KEY=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=

wrangler secret — 프로덕션 시크릿

# 시크릿 등록 (입력값은 터미널에 표시되지 않음)
npx wrangler secret put API_SECRET

# 파이프로 전달 (CI/CD에서 사용)
echo "my-secret-value" | npx wrangler secret put API_SECRET

# 등록된 시크릿 목록 확인 (값은 볼 수 없음)
npx wrangler secret list

# 시크릿 삭제
npx wrangler secret delete API_SECRET

등록된 시크릿은 Cloudflare 서버에 암호화 저장된다. wrangler secret list 를 해도 값은 보이지 않고 키 이름만 출력된다.

흔한 실수wrangler secret put 으로 등록한 값은 wrangler.toml[vars] 에 나타나지 않는다. 두 시스템은 분리되어 있으므로 같은 이름의 변수가 양쪽에 있으면 시크릿이 우선한다.


환경별 분리 — dev / staging / production

wrangler.toml[env] 블록으로 환경을 분리한다.

# wrangler.toml
name = "my-worker"
main = "src/index.ts"

# 기본값 (환경 지정 없을 때)
[vars]
APP_ENV = "development"

# staging 환경
[env.staging]
name = "my-worker-staging"
[env.staging.vars]
APP_ENV = "staging"

# production 환경
[env.production]
name = "my-worker-prod"
[env.production.vars]
APP_ENV = "production"

# D1 바인딩도 환경별로 분리
[[env.staging.d1_databases]]
binding = "DB"
database_name = "my-db-staging"
database_id = "xxxxxxxx-staging-id"

[[env.production.d1_databases]]
binding = "DB"
database_name = "my-db-prod"
database_id = "xxxxxxxx-prod-id"
# 환경별 배포
npx wrangler deploy --env staging
npx wrangler deploy --env production

# 환경별 시크릿 등록
npx wrangler secret put API_SECRET --env staging
npx wrangler secret put API_SECRET --env production

코드에서는 환경 이름을 신경 쓸 필요 없다. env.APP_ENV 를 읽으면 Cloudflare가 환경에 맞는 값을 주입한다.


TypeScript 타입 정의

바인딩과 환경 변수를 타입으로 정의하면 오타를 컴파일 단계에서 잡을 수 있다.

// src/types.ts
export type Env = {
  // D1 데이터베이스
  DB: D1Database

  // KV 네임스페이스
  CACHE: KVNamespace

  // R2 버킷
  BUCKET: R2Bucket

  // 시크릿 / 환경 변수 (항상 string)
  API_SECRET: string
  STRIPE_SECRET_KEY: string
  APP_ENV: 'development' | 'staging' | 'production'
}
// src/index.ts
import { Hono } from 'hono'
import type { Env } from './types'

const app = new Hono<{ Bindings: Env }>()

app.get('/config', (c) => {
  // c.env.APP_ENV 는 string 리터럴 유니온 타입
  return c.json({ env: c.env.APP_ENV })
})

CI/CD — GitHub Actions에서 배포

# .github/workflows/deploy.yml
name: Deploy to Cloudflare Workers

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx wrangler deploy --env production
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

GitHub Repository → Settings → Secrets and variables → Actions 에서 CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID 를 등록한다. Workers 시크릿(API_SECRET 등)은 wrangler secret put 으로 미리 등록해두면 재배포 시 그대로 유지된다.


핵심 정리

import KeyPoints from ’../../components/KeyPoints.astro’

<KeyPoints points={[ “wrangler.toml [vars] — 공개 설정값만. 커밋 가능하지만 암호화 없음”, “.dev.vars — 로컬 시크릿. 반드시 .gitignore 처리, .dev.vars.example 을 커밋해 팀 공유”, “wrangler secret put — 프로덕션 시크릿. Cloudflare 서버 암호화 저장, 값 조회 불가”, “[env.staging] / [env.production] 블록으로 D1·KV 바인딩을 환경별로 완전 분리”, “TypeScript type Env 로 모든 바인딩과 환경 변수를 타입화 — 런타임 오타 사전 차단” ]} />

import Callout from ’../../components/Callout.astro’

**다음 챕터 연결** — 시크릿 관리로 보안 기반을 잡았다면 이제 상태가 필요한 시나리오를 다룰 차례다. 다음 챕터에서는 **Durable Objects** 로 WebSocket 연결 유지, 분산 카운터, 락(lock) 패턴을 구현하는 방법을 배운다.