Docker Postgres 컨테이너 비밀번호 노출없이 안전하게 주입하는 법
이 글에서 다루는 내용은
Docker 공식 이미지(Postgres/MySQL 등)에 흔히 쓰는 -e POSTGRES_PASSWORD=... 방식이 왜 보안상 취약한지, 컨테이너에 들어갈 수만 있으면 어떻게 비밀번호가 그대로 노출되는지, 그리고 .env 파일 · POSTGRES_PASSWORD_FILE · Docker Secrets 세 가지 대안의 장단점과 선택 기준이다.
왜 이런 고민이 필요한가
로컬 개발용으로 Postgres 컨테이너를 띄울 때 아래와 같이 사용하는 경우가 있다
docker run -d --name my-postgres \
-e POSTGRES_USER=myuser \
-e POSTGRES_PASSWORD=mypassword \
-e POSTGRES_DB=mydb \
-p 5432:5432 \
postgres:16
간편하고 문서 예제에도 그대로 나와 있다. 그런데 이 비밀번호는 컨테이너에 들어갈 수 있는 누구에게나 평문으로 노출된다.
$ docker exec my-postgres env | grep POSTGRES
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
한 줄이면 끝난다. 게다가 컨테이너 메타데이터에도 남는다.
$ docker inspect my-postgres | grep -i password
"POSTGRES_PASSWORD=mypassword",
docker inspect는 일반 사용자도 Docker 그룹에 속해 있으면 실행 가능하다. CI 로그, 모니터링 툴, 사이드카 컨테이너 — 어디서든 이 값이 새어나갈 수 있다.
이게 왜 문제인가 — 공격 시나리오
1. 공유 개발 서버의 타 프로젝트 팀원같은 Docker 호스트를 여러 팀이 공유하면 A 팀의 Postgres 비밀번호가 B 팀 사용자에게 docker inspect 한 번으로 노출된다.
2. CI/CD 파이프라인 로그docker run 명령이 파이프라인 스크립트에 -e POSTGRES_PASSWORD=xxx로 박혀 있으면 실패 로그에 통째로 남는다. GitHub Actions, GitLab CI 로그는 조직 내 여러 명이 본다.
3. 컨테이너 이미지 취약점 공격Postgres 자체 CVE나 사이드카 컨테이너 침투로 쉘을 얻으면 env 명령 한 번에 비밀번호 확보. DB 자격증명이 곧 데이터 전체 접근권이다.
4. 백업/로그 유출컨테이너 런타임 로그, docker events 스트림, 모니터링 에이전트가 수집하는 메타데이터에 환경변수가 포함되는 경우가 많다.
로컬 개발에서도 지켜야 하나
상황에 따라 다르다.
- 개인 PC, 1인 개발, 외부 노출 없음: 사실상 위험 없음.
my-postgres는 localhost에만 바인딩되고 나만 접근. 지금 대응 안 해도 문제 없음. - 팀 공유 Docker 호스트: 즉시 조치 필요.
- CI/CD에서 같은 패턴 사용: 로그에 남는 순간 조직 전체 유출 리스크. 즉시 조치 필요.
- 운영 환경: 이 방식 자체가 금지 영역. 반드시 대안 사용.
대안 1 — .env 파일 + env_file: 참조
가장 현실적인 첫걸음. docker-compose 파일에 비밀번호를 박지 않고 별도 .env 파일로 분리한다.
.env
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
docker-compose.yml
services:
postgres:
image: postgres:16
env_file:
- .env
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
.gitignore
.env
효과
- git에 비밀번호가 올라가지 않는다. 유출 경로 하나 차단.
- 팀원마다 로컬
.env를 각자 가져가니 개인별 분리 가능.
여전한 한계
docker exec env로는 그대로 보인다. 환경변수 자체는 컨테이너 내부에 동일하게 주입되므로 노출 경로가 완전히 사라지지 않는다.- 그래도 가장 큰 유출원인 git 커밋은 막는다. 투자 대비 효과가 크다.
대안 2 — POSTGRES_PASSWORD_FILE — 파일 경로 주입
Postgres 공식 이미지는 비밀번호를 환경변수 대신 파일 경로로 받는 옵션을 제공한다.
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_DB: mydb
volumes:
- ./secrets/postgres_password:/run/secrets/postgres_password:ro
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
secrets/postgres_password
mypassword
.gitignore
secrets/
효과
docker exec env로는 파일 경로만 노출된다. 비밀번호 자체는 파일 안에 있으므로 노출 폭이 줄어든다.- 컨테이너가 파일을 읽는 것은 초기화 시점뿐. 그 이후엔 메모리에만 존재.
한계
- 컨테이너에 들어갈 수 있으면
cat /run/secrets/postgres_password로 결국 읽힌다. 근본적인 방어는 아님. - 파일 권한(
chmod 600) 관리가 추가 운영 포인트.
대안 3 — Docker Secrets — Swarm/Compose 공식 비밀 관리
Docker Swarm과 최신 Compose(docker compose v2)는 secrets를 1급 객체로 지원한다.
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
POSTGRES_DB: mydb
secrets:
- postgres_password
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
secrets:
postgres_password:
file: ./secrets/postgres_password
volumes:
pgdata:
효과
- Compose가 secret 파일을 tmpfs(메모리)에 마운트. 디스크에 덤프되지 않는다.
- Swarm 모드에선 secret이 raft 로그에 암호화되어 보관되고 전송도 TLS.
docker inspect에 secret 값은 노출되지 않고 참조(ID)만 보인다.
한계
- 로컬 개발 compose에선 결국 로컬 파일 읽어 들이는 거라 대안 2와 큰 차이 없음. 진짜 이점은 Swarm/Kubernetes(k8s Secret) 레벨부터.
운영 환경은 어떻게 해야 하나
로컬 .env를 가져다 그대로 운영에 쓰면 안 된다. 운영은 별도 secret 관리 시스템을 쓴다.
- Kubernetes Secret (+ Sealed Secrets, External Secrets Operator)
- AWS Secrets Manager / Parameter Store
- GCP Secret Manager
- HashiCorp Vault
공통 패턴은 "애플리케이션이 시작할 때 secret 저장소에서 값을 가져와 환경변수 또는 임시 파일로 주입". 비밀번호가 이미지에도, git에도, CI 로그에도 남지 않는다.
그럼 로컬은 뭘 써야 하나 — 선택 기준
| 상황 | 권장 |
|---|---|
| 1인 개발, localhost only | 현재 방식 유지 OK. 신경 쓸 단계 아님. |
| 팀 공유 dev 서버 | .env + env_file: (대안 1) 최소. |
| CI/CD에 같은 compose 사용 | POSTGRES_PASSWORD_FILE (대안 2). |
| 운영/스테이징 | 클라우드 secret 매니저 필수. |
로컬에서 대안 1까지만 해두면 git 유출 리스크는 막힌다. 실무에서 가장 흔한 사고가 docker-compose.yml을 public repo에 그대로 푸시하는 경우라, 이것만 막아도 대부분의 위험은 차단된다.
결론
-e POSTGRES_PASSWORD=xxx는 공식 문서에 있지만 "쓰지 마"의 예외가 아니라 "데모용"의 예시다. 로컬·CI·운영 모두 .env 파일 분리 이상의 조치를 기본값으로 두는 습관이 안전하다. 특히 github 같은 공개 저장소에 docker-compose 파일을 올리는 순간 검색 봇이 크리덴셜을 수집해가는 사례는 매년 수천 건 보고된다. 최소한 .env + .gitignore로 방어선 하나는 박아두자.