환경 변수 & Secrets
왜 시크릿 관리가 중요한가
Workers 코드에 API 키를 하드코딩하면 GitHub에 커밋하는 순간 전 세계에 공개된다. Cloudflare Workers는 환경 변수를 코드 바깥에서 주입하는 세 가지 메커니즘을 제공한다.
| 방식 | 저장 위치 | 암호화 | 용도 |
|---|---|---|---|
wrangler.toml vars | 파일(코드와 함께) | ❌ | 공개해도 무방한 설정값 |
.dev.vars | 로컬 파일(gitignore) | ❌ | 로컬 개발용 시크릿 |
wrangler secret | Cloudflare 서버 | ✅ | 프로덕션 시크릿 |
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_TOKEN 과 CLOUDFLARE_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’