발단: "서버 띄우고 확인해보자"
교회 재정 관리 앱에 브랜딩 설정 기능을 추가했다. 사이드바의 "Oikos" 텍스트 대신 교회 CI 로고를 넣을 수 있게. 코드는 다 짰고, 확인만 하면 되는 상황. 가벼운 마음으로 개발 서버를 띄우려 했다.
docker compose up -d db
포트 5432가 이미 사용 중입니다.
docker ps를 쳐보니 contents_hub_db라는 다른 프로젝트의 PostgreSQL이 떡하니 5432를 점유하고 있었다. 별거 아니지, 저걸 멈추고 다시 띄우면 되니까.
전개: 볼륨을 날리다
그런데 docker compose up -d db를 해도 포트 바인딩이 안 잡혔다. orphan 컨테이너가 꼬인 것 같았다. 깔끔하게 정리하자는 생각에 이 명령을 쳤다.
docker compose down --remove-orphans -v
-v. 이 플래그 하나가 모든 것을 바꿨다.
-v는 Docker 볼륨을 삭제한다. pgdata 볼륨 안에는 개발 DB의 모든 데이터가 들어있었다. 계정, 거래 내역, 예산, 마감 기록. 전부 날아갔다.
위기: !의 저주
볼륨이 날아갔으니 DB를 새로 만들어야 했다. docker compose up -d db로 새 컨테이너를 띄웠다. docker-compose.yml에 비밀번호가 Oikos1!로 설정되어 있으니 자동으로 초기화될 거라 생각했다.
POSTGRES_PASSWORD: "Oikos1!"
psql CLI로 접속하면 된다. Python psycopg2로 접속하면 안 된다.
FATAL: password authentication failed for user "oikos"
여기서부터 삽질이 시작됐다.
같은 비밀번호, 네 가지 표기법
Oikos1!라는 비밀번호가 환경마다 다르게 표현되어야 했다.
| 컨텍스트 | 표기법 |
|---|---|
| YAML (docker-compose) | Oikos1! |
| URL (DATABASE_URL) | Oikos1%21 |
| Shell (bash) | 'Oikos1!' (작은따옴표 필수) |
| Python psycopg2 | libpq 버전마다 다름 |
Docker가 컨테이너를 초기화할 때 Oikos1!를 scram-sha-256으로 해싱했는데, 이 해시와 psycopg2가 보내는 인증 정보가 맞지 않았다. psql CLI는 되고 Python은 안 되는 기묘한 상황.
trust → md5 → 비밀번호 리셋 → 또 실패
pg_hba.conf를 trust로 바꾸면 된다 → 비밀번호 리셋 → 다시 scram으로 복원 → 또 실패. md5로 바꿔보자 → 비밀번호 리셋 → 컨테이너 재시작하면 pg_hba가 원래대로 → 또 실패.
이 루프를 몇 번이나 돌았는지 모른다.
결국 답은 단순했다
# 특수문자 없는 비밀번호로 테스트
ALTER USER oikos WITH PASSWORD 'oikos123';
된다. !만 빼면 모든 게 작동한다.
결말: 세 줄의 교훈
삽질 끝에 정리한 것:
1. docker compose down -v는 절대 쓰지 마라
-v는 볼륨을 삭제한다. 개발 DB의 모든 데이터가 날아간다. 포트 충돌이면 충돌을 일으키는 컨테이너를 docker stop으로 멈추면 된다. 볼륨을 건드릴 이유가 없다.
2. 비밀번호에 특수문자를 쓰지 마라 (로컬 개발)
!는 YAML에서 그대로, URL에서 %21, Shell에서 따옴표 필수, scram-sha-256에서 해시 호환성 문제. 같은 문자열인데 네 가지 표기법이 필요하면 실수는 시간문제다. oikos123처럼 URL-safe 문자만 쓰면 모든 컨텍스트에서 동일하다.
3. 시크릿은 한 곳에서 관리하라
SSH 키 경로가 CLAUDE.md에 하드코딩, DB 비밀번호가 docker-compose.yml과 .env에 중복, 운영 서버 .env는 서버에만 존재. 이런 상태에서 DB가 날아가면 복구할 정보가 흩어져 있다. 결국 secrets-index로 경로를 통합하고, .env.dev와 .env.prod를 분리해서 백업 체계를 만들었다.
후기
"서버 띄우고 확인해보자"로 시작한 일이 DB 초기화, 비밀번호 삽질, 시크릿 관리 체계 정비까지 이어졌다. 원래 하려던 브랜딩 기능 확인은 1시간 뒤에야 할 수 있었다.
느낌표 하나의 무게를 알게 된 날이었다.