티스토리 뷰
백엔드에서 환경변수와 시크릿 관리는 어떻게 해야 할까? .env, 설정 분리, 운영 배포까지 FastAPI · Spring Boot · Node.js 실전 정리
octo54 2026. 6. 3. 10:12백엔드에서 환경변수와 시크릿 관리는 어떻게 해야 할까? .env, 설정 분리, 운영 배포까지 FastAPI · Spring Boot · Node.js 실전 정리
한 줄 요약
백엔드 설정은 코드에 하드코딩하지 말고 환경변수와 외부 설정으로 분리하는 게 기본입니다. FastAPI는 Pydantic Settings로 .env와 환경변수를 다룰 수 있고, Spring Boot는 외부 설정 파일·환경변수·명령줄 인자를 함께 지원합니다. Node.js도 process.env로 환경변수를 읽고, 최근 버전에서는 --env-file로 .env 파일을 직접 불러올 수 있습니다. 12-Factor App 역시 설정은 코드에서 분리해 환경변수로 관리하라고 권장합니다. (FastAPI)
이 글에서 다루는 내용
- 환경변수와 시크릿이 왜 중요한지
- .env 파일은 어디까지 써도 되는지
- FastAPI, Spring Boot, Node.js에서 설정 객체를 어떻게 만들지
- 개발/운영 환경 분리를 어떻게 할지
- 자주 터지는 실수와 운영 팁
- 검색에 잘 걸리도록 질문형 제목, 첫 문단 정답 요약, 정의 문장, 실행 코드, FAQ 구조를 반영한 글 구성 원칙
환경변수와 시크릿이란?
환경변수는 애플리케이션이 실행될 때 바깥에서 주입받는 설정값입니다. 데이터베이스 URL, JWT 시크릿, 외부 API 키, 포트 번호 같은 값이 대표적이죠. Spring Boot는 이런 설정을 properties/YAML, 환경변수, 명령줄 인자 등 다양한 외부 소스에서 읽을 수 있다고 설명합니다. Node.js는 process.env에 프로세스 시작 시점의 환경변수가 담긴다고 설명하고요. (Home)
시크릿은 그중에서도 특히 민감한 값입니다.
- DB 비밀번호
- JWT secret
- Stripe secret key
- webhook signing secret
- OAuth client secret
이건 진짜 코드에 박으면 안 됩니다.
솔직히 저도 초반엔 application.yml이나 .env 하나에 이것저것 다 넣고 끝내곤 했는데, 운영 붙고 팀원이 늘면 그 습관이 제일 먼저 발목을 잡더라고요.
왜 코드에 하드코딩하면 안 될까
이건 너무 당연한 얘기처럼 보이는데, 실무에선 정말 자주 나옵니다.
SECRET_KEY = "my-super-secret-key"
DATABASE_URL = "postgresql://admin:1234@localhost:5432/app"
이렇게 시작하면 당장은 빨라요.
근데 바로 문제가 생깁니다.
- 개발/운영 환경이 달라질 때 코드 수정이 필요함
- 배포마다 값이 달라지는데 커밋이 섞임
- 실수로 Git에 올라갈 수 있음
- 테스트 환경 분리가 어려움
12-Factor App은 설정을 코드와 분리하고, 환경마다 달라지는 값은 환경변수로 관리해야 한다고 설명합니다. Spring Boot도 같은 코드로 여러 환경에서 실행할 수 있도록 외부 설정을 지원한다고 설명합니다. (Home)
즉 진짜 핵심은 이거예요.
코드는 같고, 설정만 바뀌어야 한다.
이 문장 하나가 백엔드 운영 기본기라고 생각해도 거의 맞습니다.
.env 파일은 어디까지 써도 될까
저는 이 질문을 진짜 많이 받았어요.
“.env 쓰면 되는 거죠?”
맞습니다. 그런데 반만 맞아요.
.env가 잘 맞는 상황
- 로컬 개발
- 개인 프로젝트
- Docker Compose 로컬 실행
- 간단한 테스트 환경
.env만 믿으면 위험한 상황
- 운영 서버
- 여러 사람이 공유하는 배포 환경
- CI/CD
- 보안 규정이 있는 프로젝트
FastAPI 문서는 Pydantic Settings와 함께 .env 파일을 사용할 수 있다고 설명합니다. Node.js 공식 문서도 .env 파일 형식과 --env-file 지원을 문서화합니다. 다만 운영에선 실제 환경변수가 .env보다 우선하고, Node.js도 환경과 파일에 같은 변수가 있으면 환경변수 값이 우선한다고 설명합니다. (FastAPI)
그래서 제 기준은 이렇습니다.
- 로컬 개발: .env 적극 사용
- 운영: secret manager, 배포 플랫폼 환경변수, CI/CD secret 사용
- 공통: .env 파일은 Git에 올리지 않기
설정은 “문자열 몇 개 읽기”가 아니라 “설정 객체”로 다뤄야 한다
이건 꽤 중요한 감각입니다.
초반엔 이렇게 많이 하죠.
db_url = os.getenv("DATABASE_URL")
secret = os.getenv("JWT_SECRET")
debug = os.getenv("DEBUG")
문제는 이 방식이 금방 흩어집니다.
그래서 저는 설정도 하나의 객체로 묶는 걸 강하게 추천해요.
왜냐면 설정은 사실 이런 요구가 있기 때문입니다.
- 타입 변환
- 기본값
- 필수값 검증
- 테스트 override
- 환경별 분리
FastAPI는 Pydantic Settings를 사용해 설정을 모델처럼 다룰 수 있다고 설명하고, Spring Boot는 @ConfigurationProperties로 구조화된 설정 바인딩을 지원합니다. Node.js도 process.env 기반이지만, 애플리케이션 쪽에서 별도 config 객체를 만들어 검증하는 패턴이 훨씬 안전합니다. (FastAPI)
1) FastAPI에서 환경변수와 시크릿 관리하기
FastAPI는 공식적으로 Pydantic Settings 사용을 안내합니다. .env 파일을 함께 쓸 수 있고, @lru_cache로 설정 객체를 한 번만 만들도록 하는 패턴도 문서에 나옵니다. (FastAPI)
설치 방법
pip install fastapi uvicorn pydantic-settings
FastAPI는 pydantic-settings를 선택적 의존성으로 안내합니다. (FastAPI)
예제 코드
# app/config.py
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "backend-series-fastapi"
debug: bool = False
database_url: str
jwt_secret: str
stripe_secret_key: str
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
@lru_cache
def get_settings() -> Settings:
return Settings()
# app/main.py
from fastapi import FastAPI
from app.config import get_settings
settings = get_settings()
app = FastAPI(title=settings.app_name)
@app.get("/health")
def health():
return {
"app_name": settings.app_name,
"debug": settings.debug,
"database_url_exists": bool(settings.database_url),
}
.env 예시
APP_NAME=backend-series-fastapi-local
DEBUG=true
DATABASE_URL=postgresql://app_user:app_password@localhost:5432/app_db
JWT_SECRET=super-secret-jwt-key
STRIPE_SECRET_KEY=sk_test_example
실행 방법
uvicorn app.main:app --reload
FastAPI에서 핵심 감각
FastAPI 쪽은 진짜 이 패턴이 깔끔합니다.
- 설정은 Settings 클래스로 묶기
- .env는 로컬에서만
- 실제 코드에서는 get_settings()만 쓰기
- 필수값 누락되면 시작 시 바로 실패하기
이게 좋습니다.
왜냐면 앱이 반쯤 뜬 상태에서 “어? 시크릿 없네” 하고 런타임에 터지는 것보다, 시작하자마자 죽는 게 훨씬 낫거든요.
그리고 공식 문서처럼 @lru_cache를 써서 설정 객체를 요청마다 다시 읽지 않게 하는 습관도 좋습니다. (FastAPI)
2) Spring Boot에서 환경변수와 시크릿 관리하기
Spring Boot는 이 파트가 정말 강합니다. 외부 설정을 properties/YAML, 환경변수, 명령줄 인자 등으로 받을 수 있고, 구조화된 설정 바인딩도 공식 지원합니다. 환경변수는 보통 대문자+언더스코어 형태로 쓰고, Spring Boot가 이를 속성명으로 바인딩할 수 있다고 설명합니다. (Home)
application.yml
app:
name: backend-series-spring
debug: false
security:
jwt-secret: ${JWT_SECRET}
payment:
stripe-secret-key: ${STRIPE_SECRET_KEY}
datasource:
url: ${DATABASE_URL}
설정 클래스
// AppProperties.java
package com.example.backend.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app")
public record AppProperties(
String name,
boolean debug
) {
}
// SecurityProperties.java
package com.example.backend.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "security")
public record SecurityProperties(
String jwtSecret
) {
}
// PaymentProperties.java
package com.example.backend.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "payment")
public record PaymentProperties(
String stripeSecretKey
) {
}
// Application.java
package com.example.backend;
import com.example.backend.config.AppProperties;
import com.example.backend.config.PaymentProperties;
import com.example.backend.config.SecurityProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@ConfigurationPropertiesScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// ConfigController.java
package com.example.backend.controller;
import com.example.backend.config.AppProperties;
import com.example.backend.config.PaymentProperties;
import com.example.backend.config.SecurityProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigController {
private final AppProperties appProperties;
private final SecurityProperties securityProperties;
private final PaymentProperties paymentProperties;
public ConfigController(
AppProperties appProperties,
SecurityProperties securityProperties,
PaymentProperties paymentProperties
) {
this.appProperties = appProperties;
this.securityProperties = securityProperties;
this.paymentProperties = paymentProperties;
}
@GetMapping("/health")
public Object health() {
return java.util.Map.of(
"appName", appProperties.name(),
"debug", appProperties.debug(),
"hasJwtSecret", securityProperties.jwtSecret() != null,
"hasStripeSecret", paymentProperties.stripeSecretKey() != null
);
}
}
실행 예시
export JWT_SECRET=super-secret-jwt-key
export STRIPE_SECRET_KEY=sk_test_example
export DATABASE_URL=jdbc:postgresql://localhost:5432/app_db
./gradlew bootRun
Spring Boot에서 핵심 감각
Spring Boot는 “외부화된 설정”이라는 개념이 아주 강하게 잡혀 있습니다. 같은 코드로 여러 환경에서 실행할 수 있도록 설계된 느낌이죠. application.yml은 구조를 잡고, 민감한 실제 값은 환경변수로 주입하는 조합이 특히 무난합니다. (Home)
그리고 개인적으로 Spring에서 제일 좋은 건
설정도 record/class로 묶어서 타입 있게 다룰 수 있다는 점이에요.
이게 나중에 설정이 많아질수록 진짜 편합니다.
3) Node.js에서 환경변수와 시크릿 관리하기
Node.js는 기본적으로 process.env를 통해 환경변수를 읽습니다. 공식 문서도 이걸 가장 기본 경로로 설명하고, .env 파일 형식과 --env-file 지원도 문서화하고 있습니다. 같은 변수가 환경과 파일에 모두 있으면 환경변수 값이 우선합니다. (Node.js)
config.js
// config.js
function requireEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
export const config = {
appName: process.env.APP_NAME ?? "backend-series-node",
debug: process.env.DEBUG === "true",
databaseUrl: requireEnv("DATABASE_URL"),
jwtSecret: requireEnv("JWT_SECRET"),
stripeSecretKey: requireEnv("STRIPE_SECRET_KEY"),
};
server.js
import express from "express";
import { config } from "./config.js";
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/health", (req, res) => {
res.json({
appName: config.appName,
debug: config.debug,
hasDatabaseUrl: Boolean(config.databaseUrl),
hasJwtSecret: Boolean(config.jwtSecret),
hasStripeSecret: Boolean(config.stripeSecretKey),
});
});
app.listen(PORT, () => {
console.log(`server running on http://localhost:${PORT}`);
});
.env 예시
APP_NAME=backend-series-node-local
DEBUG=true
DATABASE_URL=postgresql://app_user:app_password@localhost:5432/app_db
JWT_SECRET=super-secret-jwt-key
STRIPE_SECRET_KEY=sk_test_example
PORT=3000
실행 방법
Node.js는 .env 파일을 CLI에서 직접 로드할 수 있습니다. (Node.js)
node --env-file=.env server.js
또는 운영 환경에서는 플랫폼 환경변수로 직접 주입하면 됩니다.
Node.js에서 핵심 감각
Node.js는 자유도가 높은 대신, 설정이 쉽게 여기저기 흩어집니다.
그래서 저는 무조건 config 객체 하나로 모으는 걸 추천해요.
- process.env를 서비스 코드 곳곳에서 직접 읽지 않기
- 필수값은 시작 시점에 검증하기
- 문자열 "true"/"false" 변환을 한 곳에서 끝내기
이 습관이 없으면 진짜 금방 지저분해집니다.
개발 환경과 운영 환경은 어떻게 나누는 게 좋을까
저는 이 기준이 제일 현실적이었습니다.
로컬 개발
- .env
- 예제용 .env.example
- 실제 .env는 Git 제외
스테이징/운영
- 배포 플랫폼 환경변수
- CI/CD secret
- 클라우드 secret manager
Spring Boot는 같은 앱 코드로 다양한 외부 설정 소스를 사용할 수 있고, Node.js도 환경변수와 .env 파일 우선순위를 명확히 둡니다. FastAPI 역시 .env와 환경변수를 같이 다룰 수 있습니다. (Home)
.env.example 예시
APP_NAME=your-app-name
DEBUG=false
DATABASE_URL=
JWT_SECRET=
STRIPE_SECRET_KEY=
PORT=3000
이 파일은 진짜 중요합니다.
팀원이 새로 들어왔을 때도 좋고, 미래의 나를 살립니다.
어떤 값이 환경변수로 빠져야 할까
이건 생각보다 애매해하는 분이 많아요.
제 기준은 이렇습니다.
거의 항상 환경변수로
- DB URL / 비밀번호
- JWT secret
- 외부 API key
- webhook secret
- Redis URL
- SMTP 계정 정보
상황에 따라
- 포트
- debug flag
- 로그 레벨
- feature flag
- CORS origin 목록
코드에 둬도 되는 편
- 진짜 변하지 않는 상수
- 도메인 규칙
- 비즈니스 정책 기본값
12-Factor App은 환경마다 달라질 수 있는 설정을 코드에서 분리하라고 설명합니다. 즉 “환경별로 바뀌는가?”가 가장 좋은 기준입니다. (GitHub)
운영에서 자주 하는 실수
1. .env를 Git에 올림
이건 정말 흔하고, 정말 위험합니다.
2. 시크릿을 로그에 찍음
console.log(config) 같은 거 초반엔 편한데, 운영에선 사고 납니다.
3. 설정이 여러 파일에 흩어짐
서비스마다 os.getenv()/process.env를 직접 읽기 시작하면 나중에 정리 안 됩니다.
4. 필수값 누락을 런타임에 발견
앱 시작 시점에 검증하는 게 훨씬 낫습니다.
5. 운영값을 application.yml이나 코드에 하드코딩
Spring Boot가 외부 설정을 지원하는 이유가 바로 이걸 피하려는 거예요. (Home)
실무에서 주의할 점
1. 시크릿과 일반 설정을 구분하세요
모든 환경변수가 같은 무게는 아닙니다.
API key, DB password, signing secret은 별도 관리 대상으로 보는 게 맞습니다.
2. “설정 로딩 실패 = 앱 시작 실패”가 낫습니다
이건 은근히 중요해요.
반쯤 켜진 상태에서 나중에 터지는 것보다, 아예 시작 시점에 죽는 게 훨씬 낫습니다.
3. 테스트 오버라이드가 쉬운 구조가 좋습니다
FastAPI 문서도 dependency 기반 settings를 쓰면 테스트가 쉬워진다고 설명합니다. (FastAPI)
4. 운영에서 .env보다 플랫폼 환경변수가 더 낫습니다
Node.js도 --env-file을 지원하지만, 운영에선 보통 배포 환경 자체의 환경변수 주입이 더 자연스럽습니다. (Node.js)
5. 설정 이름 규칙을 팀에서 맞추세요
예:
- DATABASE_URL
- JWT_SECRET
- STRIPE_SECRET_KEY
이름 규칙이 흔들리면 운영할수록 피곤해집니다.
자주 발생하는 오류
오류 1. “로컬에서는 되는데 서버에선 설정이 안 읽혀요”
원인:
- .env 파일이 배포 서버에 없음
- 작업 디렉터리가 달라서 .env를 못 찾음
- 운영 환경에 실제 환경변수가 없음
해결:
- 로컬은 .env
- 운영은 플랫폼 환경변수
- 필수값은 시작 시점에 검증
오류 2. “Spring Boot에서 환경변수 이름이 매핑이 안 돼요”
원인:
- 이름 규칙 불일치
- 하이픈/언더스코어/대소문자 감각 부족
해결:
- 보통 환경변수는 UPPER_SNAKE_CASE
- Spring 속성은 kebab-case나 camelCase로 바인딩 가능 (Home)
오류 3. “Node.js에서 .env 값이 안 먹어요”
원인:
- --env-file 없이 실행
- 이미 셸 환경변수가 같은 이름으로 설정되어 있어서 그 값이 우선됨
해결:
- node --env-file=.env server.js
- 같은 키가 있으면 환경변수가 우선한다는 점 확인 (Node.js)
오류 4. “FastAPI에서 요청마다 .env를 다시 읽는 것 같아요”
원인:
- settings 객체를 매번 생성
해결:
- 문서처럼 @lru_cache 사용 (FastAPI)
FAQ
Q. .env 파일만 잘 쓰면 운영도 괜찮나요?
로컬 개발엔 좋지만, 운영에선 보통 플랫폼 환경변수나 secret manager가 더 낫습니다. .env는 개발 편의용으로 보는 게 안전합니다. (Node.js)
Q. 설정도 DTO처럼 클래스로 만드는 게 왜 좋나요?
타입 검증, 기본값, 필수값 체크, 테스트 override가 쉬워집니다. FastAPI는 Pydantic Settings, Spring Boot는 @ConfigurationProperties로 이걸 공식 지원합니다. (FastAPI)
Q. Node.js도 dotenv 패키지 없이 갈 수 있나요?
네. 공식 문서 기준 최근 Node.js는 --env-file로 .env 파일을 직접 로드할 수 있습니다. (Node.js)
Q. 환경변수 이름 규칙은 꼭 대문자여야 하나요?
필수는 아니지만, 운영과 팀 협업에선 보통 대문자+언더스코어가 가장 무난합니다. Spring Boot도 환경변수는 보통 그런 형식으로 쓴다고 설명합니다. (Home)
Q. 시크릿을 API 응답이나 로그에 일부라도 보여줘도 되나요?
안 하는 게 맞습니다. 특히 운영 로그에 그대로 남는 순간 회수와 추적이 어려워집니다.
핵심 요약
- 설정은 코드에 하드코딩하지 말고 외부화해야 합니다. (Home)
- FastAPI는 Pydantic Settings, Spring Boot는 외부 설정 + @ConfigurationProperties, Node.js는 process.env + config 객체 조합이 무난합니다. (FastAPI)
- .env는 로컬 개발에 좋고, 운영은 플랫폼 환경변수/시크릿 관리가 더 적합합니다. (Node.js)
- 필수 설정은 앱 시작 시점에 검증하는 편이 낫습니다.
- 검색에 잘 걸리는 개발 글은 질문형 제목, 첫 문단 정답 요약, 정의 문장, 실행 코드, FAQ 구조가 강합니다.
출처
- FastAPI 공식 문서 — Settings and Environment Variables. (FastAPI)
- FastAPI 공식 문서 — optional dependency pydantic-settings. (FastAPI)
- Spring Boot 공식 문서 — Externalized Configuration. (Home)
- Spring Boot 공식 문서 — Common Application Properties. (Home)
- Node.js 공식 문서 — process.env와 환경변수 읽기. (Node.js)
- Node.js 공식 문서 — .env files, --env-file, 우선순위. (Node.js)
- 12-Factor App — Config. (GitHub)
- 글 구성 참고: 업로드하신 “AI 검색에 잘 걸리는 글” 가이드.
백엔드개발, FastAPI, SpringBoot, Nodejs, Express, 환경변수, 시크릿관리, .env, 설정분리, 백엔드시리즈
'study > 백엔드' 카테고리의 다른 글
- Total
- Today
- Yesterday
- LangChain
- REACT
- PostgreSQL
- JWT
- Next.js
- 개발블로그
- 웹개발
- Express
- 딥러닝
- SpringBoot
- Python
- llm
- 주니어개발자
- CI/CD
- JAX
- seo 최적화 10개
- fastapi
- SEO최적화
- node.js
- 백엔드개발
- 쿠버네티스
- nodejs
- rag
- nextJS
- Prisma
- NestJS
- kotlin
- flax
- 생성형AI
- DevOps
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |

