Engineering Note

Docker Compose는 편한데, .env 비밀값은 너무 쉽게 남는다

Pletor가 공개한 Envoyage를 소개합니다. Docker Compose 흐름은 유지하면서 평문 secret env 파일이 Git, 백업, 배포 디렉터리에 남는 위험을 줄이는 작은 도구입니다.

2026년 6월 22일 · Pletor Engineering dockerconfigurationsecurityopen-source

Docker Compose는 작고 빠른 배포 단위에 잘 맞습니다. 로컬 개발, PoC, 내부 도구, 단일 VM 운영처럼 복잡한 플랫폼을 올리기 전 단계에서는 특히 그렇습니다.

문제는 secret을 다루는 방식입니다. Compose 프로젝트에서는 자연스럽게 .env 파일이 생기고, 어느 순간 그 안에 데이터베이스 비밀번호, API key, admin password 같은 값이 들어갑니다. 그리고 이 파일은 생각보다 쉽게 남습니다.

Git에 실수로 올라간다.
백업에 같이 들어간다.
배포 디렉터리에 오래 남는다.
공유 작업공간에서 다른 사람이 보게 된다.
쉘 히스토리나 운영 스크립트 주변에 흔적이 남는다.

Vault, KMS, Kubernetes Secret, 외부 secret manager는 좋은 해법입니다. 다만 모든 Compose 기반 배포에 그 정도의 운영 체계를 붙이는 것이 항상 현실적인 것은 아닙니다. 작은 서비스나 내부 운영 도구에서는 도구의 무게가 문제 자체보다 커질 때도 있습니다.

그래서 Pletor는 Envoyage(/ˈɛn.vɔɪ.ɪdʒ/)를 오픈소스로 공개했습니다.

Envoyage는 Docker Compose를 위한 lightweight encrypted environment-variable loader입니다. .env.age 파일을 메모리에서 복호화하고, dotenv 형식으로 파싱한 뒤, 그 값을 docker compose 자식 프로세스의 환경 변수로 넘깁니다. 핵심은 복호화된 평문 .env 파일을 디스크에 다시 쓰지 않는다는 점입니다.

투명한 유리 큐브 안의 빛나는 암호화된 설정 파일에서 컨테이너 스택으로 안전한 데이터 흐름이 이어지는 일러스트
Envoyage는 평문 secret 파일을 디스크에 다시 남기지 않고, 실행 시점에만 Compose 환경으로 전달하는 작은 도구입니다.

Envoyage가 겨냥하는 자리

Envoyage는 secret manager 전체를 대체하려는 도구가 아닙니다. 오히려 위치가 더 좁고 분명합니다.

plain .env file
  -> Envoyage
  -> Vault, KMS, platform-level secret management

평문 .env만 쓰기에는 불안하지만, 중앙 secret 관리 시스템을 도입하기에는 작은 Compose 배포가 있습니다. Envoyage는 그 중간 영역을 겨냥합니다.

기본 흐름은 단순합니다.

  1. 비밀이 아닌 설정은 .env에 둡니다.
  2. 편집 중인 secret 원본은 .secrets.env에 둡니다.
  3. .secrets.env.env.age로 암호화합니다.
  4. envoyage compose ...로 Docker Compose를 실행합니다.

운영 디렉터리에는 .env.env.age만 남기고, 실제 secret 원본인 .secrets.env는 제거하거나 커밋하지 않는 식으로 관리합니다.

빠르게 써보기

저장소에는 PostgreSQL, MariaDB, Redis, MongoDB, MinIO 예제가 들어 있습니다. 아래는 PostgreSQL 예제를 기준으로 한 흐름입니다.

git clone https://github.com/pletorco/envoyage.git
cd envoyage
go build -o envoyage ./cmd/envoyage
cd examples/postgresql

예제용 age identity를 만듭니다.

../../envoyage keygen --out age-key.txt

.secrets.env.env.age로 암호화합니다.

AGE_IDENTITY_FILE=./age-key.txt ../../envoyage encrypt

이제 Compose 설정을 확인하거나 서비스를 시작할 때 Envoyage를 앞에 둡니다.

AGE_IDENTITY_FILE=./age-key.txt ../../envoyage compose -f compose.yaml config
AGE_IDENTITY_FILE=./age-key.txt ../../envoyage compose -f compose.yaml up -d

실행이 끝나면 Compose 정리는 평소처럼 해도 됩니다.

docker compose -f compose.yaml down -v

예제 저장소의 .secrets.env는 copy-and-run 데모를 위해 포함되어 있습니다. 실제 프로젝트에서는 평문 secret 파일을 커밋하지 않고, .env.age와 identity 배포 정책을 따로 정해야 합니다.

내부에서는 무엇이 달라지나

일반적인 Compose 사용자는 다음처럼 실행합니다.

docker compose up -d

Envoyage를 쓰면 다음처럼 실행합니다.

envoyage compose up -d

기본적으로 Envoyage는 현재 디렉터리의 .env.env.age를 자동으로 읽습니다. .env를 먼저 읽고, .env.age를 나중에 읽습니다. 같은 key가 있으면 나중에 읽은 값이 앞의 값을 덮습니다.

즉, 다음처럼 나눌 수 있습니다.

DB_HOST=postgres
DB_USER=app
DB_PORT=5432
DB_PASSWORD=secret-password
API_KEY=secret-api-key

첫 번째는 평문 .env에 둘 수 있는 일반 설정이고, 두 번째는 암호화 전의 .secrets.env에 두었다가 .env.age로 바꿀 값입니다.

명시적으로 파일을 지정할 수도 있습니다.

envoyage compose --env-file .env --env-file .env.age config

이때 Envoyage는 --env-file--identity 옵션을 먼저 소비합니다. 그런 다음 남은 인자를 실제 docker compose에 넘깁니다.

envoyage compose --env-file .env.age -f compose.yaml -p myapp up -d

위 명령은 내부적으로 다음 Compose 호출 형태에 가깝게 이어집니다.

docker compose -f compose.yaml -p myapp up -d

다만 Compose 프로세스에는 Envoyage가 복호화하고 병합한 환경 변수가 함께 전달됩니다.

Docker처럼 보이게 쓰는 shim mode

Envoyage 0.2.0에는 optional Docker shim mode도 추가되었습니다.

이 모드는 Envoyage binary가 docker라는 이름으로 실행될 때만 동작합니다. 보통 실제 Docker CLI보다 앞선 PATH 위치에 docker symlink를 두는 방식입니다.

envoyage shim install
export PATH="$HOME/.local/bin:$PATH"
export ENVOYAGE_DOCKER_BIN=/usr/bin/docker

이후에는 기존 스크립트가 다음처럼 실행해도,

docker compose --env-file .env.age up -d

docker compose ... 호출만 Envoyage가 가로채고, docker ps, docker version 같은 다른 Docker 명령은 실제 Docker binary로 넘깁니다.

이 모드는 기존 운영 스크립트를 크게 바꾸기 어려울 때 유용할 수 있습니다. 대신 PATH를 이용하는 방식이므로 어떤 docker가 실행되는지 분명히 관리해야 합니다. 저장소 README도 ENVOYAGE_DOCKER_BIN을 명시하는 방식을 권장합니다.

보안 경계는 어디까지인가

Envoyage가 줄여주는 위험은 “평문 secret 파일이 우연히 남는 위험”입니다. 이것은 실무에서 꽤 자주 발생하는 문제입니다.

Envoyage는 복호화된 dotenv 내용을 디스크에 쓰지 않습니다. 자체 로그나 에러 메시지에 secret value를 의도적으로 출력하지도 않습니다. 그래서 Git, 백업, 배포 디렉터리, 공유 작업공간에 평문 secret 파일이 남을 가능성을 줄일 수 있습니다.

하지만 이것은 강한 보안 경계가 아닙니다.

복호화된 값은 결국 docker compose 자식 프로세스의 환경 변수로 전달됩니다. Docker 권한이 있는 사용자, root/superuser, 컨테이너 환경 변수를 inspection할 수 있는 사용자는 secret을 볼 수 있습니다. 기본 identity 경로인 /etc/envoyage/envoyage-key.txt를 읽을 수 있는 사용자도 해당 recipient로 암호화된 .env.age를 복호화할 수 있습니다.

또한 Envoyage는 다음을 제공하지 않습니다.

  • Vault나 KMS 같은 중앙 secret 관리
  • TTL, revoke, rotation 정책
  • root나 Docker 관리자에 대한 보호
  • compose.yaml 안의 env_file: .env.age 자동 처리

이 한계를 명확히 이해해야 합니다. Envoyage는 “이제 secret 관리가 끝났다”가 아니라 “작은 Compose 운영에서 평문 secret 파일을 덜 남기자”에 가깝습니다.

언제 잘 맞는가

Envoyage가 잘 맞는 상황은 비교적 분명합니다.

  • Docker Compose 기반의 작은 배포를 운영한다.
  • .env를 계속 쓰고 싶지만 secret 값까지 평문으로 두기는 싫다.
  • Vault나 KMS를 붙이기에는 운영 부담이 크다.
  • 기존 Compose 명령과 스크립트를 최대한 유지하고 싶다.
  • Git이나 백업에 평문 secret 파일이 섞이는 사고를 줄이고 싶다.

반대로 조직 전체의 secret lifecycle, 접근 제어, 감사 로그, rotation, 동적 credential 발급이 필요하다면 중앙 secret 관리 시스템이 더 맞습니다. Kubernetes 중심 운영이라면 Kubernetes Secret, External Secrets Operator, cloud secret manager와의 통합을 먼저 검토하는 편이 자연스럽습니다.

운영 도구는 작은 습관을 줄이는 데서 시작한다

운영 사고는 대단히 복잡한 시스템에서만 생기지 않습니다. .env 하나를 잘못 커밋하거나, 배포 서버의 작업 디렉터리에 오래 남겨두거나, 백업 파일에 같이 묶어버리는 일도 충분히 문제가 됩니다.

Envoyage는 그런 작은 실수를 줄이는 도구입니다. Docker Compose의 단순함은 유지하면서, secret 값만 암호화된 파일로 옮기고, 실행 시점에만 메모리에서 풀어 Compose에 전달합니다.

Konduo도 비슷한 운영 감각 위에서 만들어지고 있습니다. 리소스, 메트릭, 알림을 보는 일뿐 아니라 설정과 배포 흐름을 단순하고 반복 가능하게 만드는 일이 운영 품질의 일부입니다. Compose 기반 PoC나 작은 운영 환경에서도 이런 기본기를 갖추면, 이후 더 큰 플랫폼으로 옮길 때도 시스템을 설명하기가 쉬워집니다.

Envoyage는 거대한 secret platform이 아닙니다. 대신 작은 Compose 운영에서 평문 secret 파일을 줄이는 데 집중합니다. 그 정도의 문제를 정확히 겨냥한다면, 도구의 크기와 얻는 효과가 꽤 잘 맞습니다.

함께 읽기 좋은 글

이 글의 관점과 이어지는 주제를 더 보려면 아래 글도 함께 읽어볼 수 있습니다.