이 글에서 다루는 내용은

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로 방어선 하나는 박아두자.