PostgreSQL 언어별 가이드

PostgreSQL 언어별 가이드

이 가이드는 Docker로 PostgreSQL 데이터베이스를 컨테이너화하는 방법을 알려줘요.

출처: 문서

본문

즉시 설정 & 데이터 유지 (Immediate setup & data persistence)

이 가이드는 5분 안에 실행 중인 PostgreSQL 컨테이너를 만드는 것부터 시작해서, 컨테이너를 재시작하거나 제거해도 데이터를 안전하게 유지하는 방법까지 알려줘요.

개요 (Overview)

Docker에서 PostgreSQL을 실행하려면 한 가지 중요한 개념을 이해해야 해요: 컨테이너는 일시적(ephemeral)이지만 당신의 데이터는 그래선 안 된다는 거예요. 이 가이드에서 다루는 내용:

  • 단일 명령으로 PostgreSQL 시작하기
  • 기본적으로 컨테이너가 데이터를 잃는 이유 이해하기
  • 영구 저장소를 위한 볼륨 구성하기
  • 구성을 Docker Compose로 옮기기

빠른 시작 (Quick start — 최소 실행 가능 컨테이너)

Note

Docker Hardened Images (DHIs)는 Docker가 관리하는 최소화되고 안전하며 프로덕션 준비가 된 컨테이너 베이스·어플리케이션 이미지예요. 보안을 위해 가능하면 DHI를 권장해요. 취약점을 줄이고 컴플라이언스를 단순화하도록 설계됐으며, 구독 없이 누구나 자유롭게 사용할 수 있고 사용 제한도 없고 벤더 락인(vendor lock-in)도 없어요.

PostgreSQL을 다음 단일 명령으로 즉시 실행해요:

DHI 사용 — DHI를 pull하려면 먼저 dhi.io에 인증해야 해요. docker login dhi.io를 실행해서 인증하세요.

docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d dhi.io/postgres:18

DOI 사용

$ docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d postgres:18

플래그 이해하기

플래그 용도
--rm 컨테이너가 중지되면 자동으로 제거
--name postgres-dev 무작위 문자열 대신 기억하기 쉬운 이름 부여
-e POSTGRES_PASSWORD=... 슈퍼유저 비밀번호 설정 (필수)
-p 5432:5432 호스트 5432 포트를 컨테이너 5432 포트에 매핑
-d 컨테이너를 백그라운드로 실행 (detached 모드)

컨테이너가 실행 중인지 확인해요:

$ docker ps --filter name=postgres-dev
CONTAINER ID   IMAGE         COMMAND                  STATUS         PORTS                    NAMES
a1b2c3d4e5f6   postgres:18   "docker-entrypoint.s…"   Up 2 seconds   0.0.0.0:5432->5432/tcp   postgres-dev

컨테이너 안에서 psql을 사용해 연결해요:

$ docker exec -it postgres-dev psql -U postgres
psql (18.0)
Type "help" for help.

postgres=#

이제 동작하는 PostgreSQL 인스턴스가 생겼어요. 그런데 문제가 있어요 — 이 컨테이너를 중지하면 데이터가 사라져요.

데이터 유지 문제 (The data persistence problem)

컨테이너는 일시적인 파일시스템을 사용해요. 컨테이너가 제거되면 그 안의 모든 것, 데이터베이스 파일을 포함해 모두 삭제돼요.

직접 확인해보세요:

$ docker exec postgres-dev psql -U postgres -c "CREATE DATABASE testdb;"
CREATE DATABASE

$ docker exec postgres-dev psql -U postgres -c "\l" | grep testdb
 testdb    | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           |

$ docker stop postgres-dev
postgres-dev

$ docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -d postgres:18

$ docker exec postgres-dev psql -U postgres -c "\l" | grep testdb
(no output - database is gone)

testdb 데이터베이스가 사라졌어요. 새 컨테이너가 새 파일시스템으로 시작됐기 때문이에요. 이것은 예상된 동작이고, 바로 그래서 볼륨이 존재하는 거예요.

명명된 볼륨 (Named volumes)

명명된 볼륨(named volumes)은 컨테이너와 독립적으로 유지되는 Docker 관리형 저장소 위치예요. Docker가 파일시스템 위치, 권한, 라이프사이클을 처리해요.

명명된 볼륨으로 컨테이너를 만들어요:

$ docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql \
  -d postgres:18

-v postgres_data:/var/lib/postgresql 플래그는 postgres_data라는 명명된 볼륨을 PostgreSQL의 데이터 디렉토리에 마운트해요. 볼륨이 없다면 Docker가 자동으로 만들어요.

Note

PostgreSQL 18+는 /var/lib/postgresql 아래의 버전별 하위 디렉토리에 데이터를 저장해요. 이 수준(/var/lib/postgresql/data가 아니라)에서 마운트하면 pg_upgrade --link를 사용한 더 쉬운 업그레이드가 가능해져요.

데이터 유지가 동작하는지 확인하기

데이터 유지를 확인하려면 이전 테스트를 반복하되, 이번에는 명명된 볼륨을 붙여서 실행해요.

$ docker exec postgres-dev psql -U postgres -c "CREATE DATABASE testdb;"
CREATE DATABASE

$ docker stop postgres-dev
postgres-dev

$ docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql \
  -d postgres:18

$ docker exec postgres-dev psql -U postgres -c "\l" | grep testdb
 testdb    | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           |

출력에 testdb가 보인다면 데이터 유지가 동작하는 거예요: 볼륨이 데이터 디렉토리를 보존했기 때문에 데이터베이스가 살아남았어요.

볼륨 관리하기

모든 볼륨 나열:

$ docker volume ls --filter name=postgres_data
DRIVER    VOLUME NAME
local     postgres_data

볼륨을 검사해 세부 정보 확인:

$ docker volume inspect postgres_data
[
    {
        "CreatedAt": "2025-01-05T10:30:00Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/postgres_data/_data",
        "Name": "postgres_data",
        "Options": null,
        "Scope": "local"
    }
]

사용하지 않는 볼륨 제거 (경고: 모든 데이터 삭제):

$ docker volume rm postgres_data

바인드 마운트 (Bind mounts — 대안)

바인드 마운트는 특정 호스트 디렉토리를 컨테이너 경로에 매핑해요. 명명된 볼륨과 달리, 데이터가 호스트 파일시스템의 정확히 어디에 살지 직접 제어할 수 있어요.

Postgres 데이터를 저장할 호스트 머신에 디렉토리를 만들어요.

mkdir -p ~/postgres-data && sudo chown -R 999:999 ~/postgres-data

바인드 마운트로 Postgres를 실행해요.

docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v ~/postgres-data:/var/lib/postgresql \
  -d dhi.io/postgres:18
$ mkdir -p ~/postgres-data

바인드 마운트로 Postgres를 실행해요.

$ docker run --rm --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v ~/postgres-data:/var/lib/postgresql \
  -d postgres:18

바인드 마운트를 언제 사용할까

바인드 마운트는 파일을 직접 읽는 백업 스크립트를 위해 데이터 디렉토리에 직접 파일시스템 접근이 필요할 때, 호스트 레벨 모니터링 도구와 통합할 때, 또는 특정 권한 요구 사항이 있을 때 유용해요. 대부분의 개발·프로덕션 시나리오에서는 명명된 볼륨이 더 간단하고 오류가 적어요.

흔한 바인드 마운트 문제

바인드 마운트에서 가장 흔한 문제는 권한 오류예요. PostgreSQL은 컨테이너 안에서 postgres 사용자(UID 999)로 실행돼요. 호스트 디렉토리에 제한적인 권한이 있다면 컨테이너가 시작되지 않아요.

컨테이너가 즉시 종료되면 로그를 확인해요:

$ docker logs postgres-dev

Docker Compose 구성 (Docker Compose configuration)

Docker Compose는 전체 구성을 파일에 담아두므로, 설정을 재현 가능하게 만들고 복잡성이 커짐에 따라 관리하기 쉽게 해줘요.

compose.yaml 파일을 만들어요:

services:
  db:
    image: postgres:18
    container_name: postgres-dev
    environment:
      POSTGRES_PASSWORD: mysecretpassword
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql

volumes:
  postgres_data:

데이터베이스를 시작해요:

$ docker compose up -d

컨테이너를 중지하고 제거해요 (볼륨은 유지):

$ docker compose down

또는 컨테이너를 중지·제거하고 볼륨까지 삭제할 수도 있어요:

$ docker compose down -v

이 compose 파일은 이후 가이드에서 다룰 초기화 스크립트 추가, 성능 튜닝, 동반 서비스(companion services)의 기반이 돼요.

환경 변수 참조 (Environment variables reference)

공식 PostgreSQL 이미지는 다음 환경 변수를 지원해요:

변수 필수 설명
POSTGRES_PASSWORD 예 슈퍼유저 비밀번호
POSTGRES_USER 아니요 슈퍼유저 이름 (기본값: postgres)
POSTGRES_DB 아니요 기본 데이터베이스 이름 (기본값: POSTGRES_USER의 값)

다음 단계 (Next steps)

영구 저장소를 구성했으니 PostgreSQL을 더 커스터마이즈할 준비가 됐어요. 이 가이드의 다음 장에서 다루는 내용:

  • 초기화 스크립트로 자동 스키마 생성
  • 컨테이너화된 워크로드에 대한 성능 튜닝
  • 시간대와 로케일 구성

고급 구성 및 초기화 (Advanced Configuration and Initialization)

이전 섹션에서 영구 저장소를 구성했으니, 이제 PostgreSQL을 실제 사용 사례에 맞게 커스터마이즈할 준비가 됐어요. 이 가이드는 Docker 컨테이너에서 PostgreSQL을 실행하기 위한 고급 구성 기법을 다뤄요 — 자동 데이터베이스 초기화, 성능 튜닝, 시간대 구성을 포함해요.

개요 (Overview)

PostgreSQL 컨테이너는 기본 설정으로 빠르게 시작할 수 있지만, 프로덕션 환경은 커스터마이즈된 구성을 요구해요. 이 가이드는 다음 방법을 설명해요:

  • 컨테이너 시작 시 데이터베이스, 스키마, 사용자 생성 자동화하기
  • 컨테이너화된 워크로드를 위한 PostgreSQL 성능 파라미터 튜닝하기
  • 시간대와 로케일 설정 구성하기

초기화 스크립트 (Initialization scripts)

공식 PostgreSQL Docker 이미지는 컨테이너가 처음 시작될 때 초기화 스크립트를 자동으로 실행하는 것을 지원해요. /docker-entrypoint-initdb.d/ 디렉토리에 넣은 모든 파일은 알파벳 순서로 실행돼요.

초기화가 동작하는 방식

컨테이너가 시작되면 PostgreSQL 데이터 디렉토리가 비어 있는지 확인해요. 디렉토리에 이미 데이터가 있다면 PostgreSQL은 아무 초기화도 실행하지 않고 즉시 시작해요. 디렉토리가 비어 있다면 컨테이너가 initdb를 실행해 새 데이터베이스 클러스터를 만든 다음, PostgreSQL을 시작하기 전에 /docker-entrypoint-initdb.d/의 모든 스크립트를 알파벳 순서로 실행해요.

지원되는 파일 형식

형식 설명
.sql 직접 실행되는 SQL 명령
.sql.gz gzip으로 압축된 SQL 파일
.sh bash로 실행되는 셸 스크립트

Important

초기화 스크립트는 PostgreSQL 데이터 디렉토리(/var/lib/postgresql/data)가 비어 있을 때만 실행돼요. 기존 데이터가 있는 볼륨을 마운트하면 초기화는 건너뛰어져요. 이 동작은 기존 데이터베이스 덮어쓰기를 방지해요.

초기화 스크립트 마운트하기

Docker Compose를 사용해 초기화 스크립트를 컨테이너에 마운트해요. 먼저 프로젝트 디렉토리를 만들어요:

$ mkdir -p postgres-project/init-db
$ cd postgres-project

compose.yaml 파일을 만들어요:

services:
  db:
    image: postgres:18
    volumes:
      - ./init-db:/docker-entrypoint-initdb.d
      - postgres_data:/var/lib/postgresql
    environment:
      POSTGRES_PASSWORD: mysecretpassword

volumes:
  postgres_data:

./init-db 디렉토리의 모든 스크립트는 컨테이너가 처음 시작될 때 실행돼요. 이것은 데이터베이스를 부트스트랩하는 데 훌륭해요.

초기화 스크립트 예제

init-db 디렉토리에 init.sql이라는 파일을 만들어요:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

이 스크립트는 컨테이너가 처음 시작될 때 자동으로 실행되어 초기 데이터베이스 스키마를 만들어요.

Note

초기화 스크립트에 올바른 읽기 권한이 있는지 확인해요. "Permission denied" 오류가 발생하면 chmod 644 init-db/*.sql을 실행해 컨테이너가 파일을 읽을 수 있게 해요.

성능 튜닝 (Performance tuning)

기본 PostgreSQL 설정은 제한된 리소스의 시스템에서 동작하도록 보수적으로 잡혀 있어요. 프로덕션 워크로드에서는 컨테이너에 할당된 리소스에 기반해 이 파라미터들을 튜닝해야 해요.

방법 1: 커스텀 구성 파일

완전한 제어를 위해 커스텀 postgresql.conf 파일을 마운트해요. 먼저 기본 구성을 추출해요:

$ docker run -i --rm postgres:18 cat /usr/share/postgresql/postgresql.conf.sample > my-postgres.conf

원하는 설정으로 my-postgres.conf를 편집한 다음, Compose 파일에 마운트해요:

services:
  db:
    image: postgres:18
    volumes:
      - ./my-postgres.conf:/etc/postgresql/postgresql.conf
      - ./init-db:/docker-entrypoint-initdb.d
      - postgres_data:/var/lib/postgresql
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    environment:
      POSTGRES_PASSWORD: mysecretpassword

volumes:
  postgres_data:

주요 구성 파라미터 (Key configuration parameters)

다음 표는 컨테이너화된 PostgreSQL 배포를 위한 중요한 postgresql.conf 파라미터를 나열해요.

연결 설정 (Connection settings)

파라미터 설명 기본값
listen_addresses 수신 대기할 IP 주소 localhost
port TCP 포트 번호 5432
max_connections 최대 동시 연결 수 100

메모리 설정 (Memory settings)

파라미터 설명 권장 시작 값
shared_buffers 캐싱용 공유 메모리 컨테이너 메모리의 25%
work_mem 쿼리 연산당 메모리 4MB - 64MB
maintenance_work_mem VACUUM, CREATE INDEX용 메모리 64MB - 256MB
effective_cache_size Planner의 캐시 크기 추정 컨테이너 메모리의 50-75%
Docker 메모리 제한

메모리 파라미터를 튜닝할 때 Compose의 deploy.resources.limits.memory 또는 docker run의 --memory로 컨테이너에 명시적인 메모리 제한을 설정해요. 제한이 없으면 PostgreSQL은 호스트의 전체 RAM을 보고 의도보다 더 많이 할당할 수 있어요. 예를 들어 컨테이너가 최대 4GB를 사용해야 한다면 shared_buffers를 약 1GB(25%)로 설정해요.

I/O 설정

파라미터 설명 권장 시작 값
effective_io_concurrency 동시 디스크 I/O 연산 SSD는 200, HDD는 2

타임아웃 설정

파라미터 설명 기본값
statement_timeout 모든 문장의 최대 시간 0 (비활성)
lock_timeout 락 대기 최대 시간 0 (비활성)
deadlock_timeout 데드락 확인 전 시간 1s
transaction_timeout 트랜잭션의 최대 시간 0 (비활성)

Note

컨테이너에서 shared_buffers를 너무 높게 설정하면 커널 공유 메모리 제한을 초과할 수 있어요. 컨테이너 메모리 제한의 25-30% 이하로만 사용하세요.

시간대와 로케일 구성 (Timezone and locale configuration)

적절한 로컬라이제이션은 타임스탬프와 정렬이 어플리케이션의 사용자에게 올바르게 동작하도록 보장해요.

services:
  db:
    image: postgres:18
    volumes:
      - postgres_data:/var/lib/postgresql
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      TZ: America/New_York

volumes:
  postgres_data:

또는 PostgreSQL 명령줄 파라미터로 시간대를 설정해요:

services:
  db:
    image: postgres:18
    command: ["postgres", "-c", "timezone=America/New_York"]
    environment:
      POSTGRES_PASSWORD: mysecretpassword

로케일 설정하기

POSTGRES_INITDB_ARGS 환경 변수를 사용해 데이터베이스 초기화 중에 로케일 설정을 지정해요:

services:
  db:
    image: postgres:18
    volumes:
      - postgres_data:/var/lib/postgresql
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8"

volumes:
  postgres_data:

이것은 콜레이션(정렬)과 문자 처리 동작에 영향을 미쳐요. 데이터베이스 생성 후 이 변수를 바꿔도 효과는 없어요 — 데이터 디렉토리가 초기화되는 첫 실행 때만 적용돼요.

데이터베이스에 연결하기 (Connecting to the database)

호스트 머신에 psql이 설치되어 있지 않아도 컨테이너에서 실행 중인 PostgreSQL과 상호작용할 수 있어요.

인터랙티브 셸

컨테이너 안에서 psql 세션을 열어요:

$ docker exec -it postgres-container psql -U postgres

특정 데이터베이스에 연결:

$ docker exec -it postgres-container psql -U postgres -d mydb

네트워킹과 연결 (Networking and connectivity)

이 가이드는 Docker에서 실행 중인 PostgreSQL에 연결하는 두 가지 일반적인 방법을 다뤄요:

  • 컨테이너 간(Container-to-container): 사설 Docker 네트워크를 통해 어플리케이션 컨테이너에서 PostgreSQL로 연결. 호스트에 포트를 노출할 필요가 없어요.
  • 호스트 간(Host-to-container): 랩톱이나 개발 머신에서 localhost와 게시된 포트를 사용해 연결.

준비 사항: 이 가이드는 PostgreSQL이 영구 저장소와 함께 실행 중이라고 가정해요. 그렇지 않다면 먼저 Immediate Setup & Data Persistence 가이드를 따르세요.

내부 네트워크 접근 (컨테이너 간)

어플리케이션이 다른 컨테이너에서 실행될 때 사용자 정의 브리지 네트워크를 통해 PostgreSQL에 연결하는 것이 권장되는 접근 방식이에요. 이 구성은 자동 DNS 해석을 제공하므로, IP 주소를 추적할 필요 없이 컨테이너 이름을 호스트네임으로 사용해 어플리케이션이 PostgreSQL에 연결할 수 있어요.

Note

기본 브리지 네트워크는 왜 안 쓸까요? 기본 브리지 네트워크의 컨테이너들도 통신할 수 있지만 IP 주소로만 가능해요. 컨테이너 IP는 재시작하면 바뀌므로 PostgreSQL 연결 문자열을 매번 업데이트해야 해요. 사용자 정의 브리지 네트워크는 자동 DNS 해석을 제공해 이 문제를 해결하며, 컨테이너가 재시작되어 새 IP를 받아도 PostgreSQL 연결 문자열이 안정적으로 유지되도록 보장해요.

다음은 접근 방식의 차이를 보여주는 빠른 비교예요:

Note

아래 예제는 접근 방식의 차이를 보여줘요. 실제로 테스트하려면 이 가이드의 단계를 따라 먼저 적절한 네트워크에 컨테이너를 구성하세요.

기본 브리지 네트워크를 쓰면 먼저 IP 주소를 찾아야 해요:

# Get the container's IP address (changes on restart)
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgres-dev
# Output: 172.17.0.2

# Then connect using that IP address from another container
# (No --network flag needed - containers default to bridge network)
docker run --rm -it \
  -e PGPASSWORD=mysecretpassword \
  postgres:18 \
  psql -h 172.17.0.2 -U postgres

사용자 정의 네트워크를 쓰면 컨테이너 이름을 그냥 사용하면 돼요:

# Container name works directly - no IP lookup needed
docker run --rm -it \
  --network my-app-net \
  -e PGPASSWORD=mysecretpassword \
  postgres:18 \
  psql -h postgres-dev -U postgres

1단계: 사용자 정의 네트워크 만들기

docker network create my-app-net

# Example Output
ab7f984be43a0ca15534a9ee568716ddbe869a5875077fad3ef3192e3af7d288

docker network ls
# Output
ab7f984be43a   my-app-net    bridge    local

2단계: 그 네트워크에서 PostgreSQL 실행하기 (포트 게시 없음)

여기에는 -p 5432:5432가 없다는 점에 주의하세요. 이것은 PostgreSQL을 Docker 내부에 두고 호스트 머신에서 접근 불가능하게 하므로 프로덕션 환경에서 더 안전해요.

docker run -d --name postgres-dev \
  --network my-app-net \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -v postgres_data:/var/lib/postgresql \
  postgres:18

  # Output
CONTAINER ID  IMAGE        COMMAND                 CREATED         STATUS        PORTS     NAMES
6d351ed89efc  postgres:18  "docker-entrypoint.s…"  9 seconds ago   Up 8 seconds  5432/tcp  postgres-dev

3단계: Postgres 컨테이너 이름으로 다른 컨테이너에서 연결하기

임시 psql 클라이언트 컨테이너로 연결성을 테스트할 수 있어요:

docker run --rm -it \
  --network my-app-net \
  -e PGPASSWORD=mysecretpassword \
  postgres:18 \
  psql -h postgres-dev -U postgres

핵심: -h postgres-dev가 동작하는 이유는 사용자 정의 네트워크에서 Docker DNS가 컨테이너 이름을 해석하기 때문이에요. 컨테이너 이름이 호스트네임 역할을 해요.

연결 문자열 예제

어플리케이션 컨테이너에서 연결할 때 다음 PostgreSQL 연결 문자열을 사용해요:

  • PostgreSQL URI 형식: 모든 연결 파라미터를 단일 문자열로 결합하는 표준 PostgreSQL 연결 URI 형식이에요. PostgreSQL 클라이언트와 라이브러리에서 널리 지원돼요.
postgresql://postgres:mysecretpassword@postgres-dev:5432/postgres

이 명령은 PostgreSQL URI 연결 문자열을 환경 변수로 컨테이너에 전달하는 방법을 보여줘요. 어플리케이션은 이 변수를 읽어 데이터베이스에 연결할 수 있어요.

Docker run 명령에서의 예시 사용:

docker run --rm -it \
    --network my-app-net \
    -e DATABASE_URL="postgresql://postgres:mysecretpassword@postgres-dev:5432/postgres" \
    alpine:latest \
    sh -c 'echo "DATABASE_URL is set to: $DATABASE_URL"'
  • PostgreSQL 연결 파라미터: 공백으로 구분된 키-값 쌍을 사용하는 형식이에요. 많은 PostgreSQL 클라이언트 라이브러리가 URI 형식의 대안으로 허용해요.
host=postgres-dev
port=5432
user=postgres
password=mysecretpassword
dbname=postgres

어플리케이션 코드에서의 예시 사용 (Python with psycopg2):

conn = psycopg2.connect(
      host="postgres-dev",
      port=5432,
      user="postgres",
      password="mysecretpassword",
      dbname="postgres"
)
  • 특정 데이터베이스 연결: 연결 문자열의 데이터베이스 이름을 바꿔서 기본 postgres 데이터베이스 대신 특정 데이터베이스에 연결해요. 커스텀 데이터베이스(예: testdb)를 만들었다면 다음을 사용해요:
postgresql://postgres:mysecretpassword@postgres-dev:5432/testdb

SSL 비활성화 예제 (Docker 네트워크에서 흔함): 사설 Docker 네트워크에서 SSL 암호화가 필요 없을 때 연결 문자열에 ?sslmode=disable을 추가해요.

postgresql://postgres:mysecretpassword@postgres-dev:5432/testdb?sslmode=disable

Note

이 예제들에서는 기본 포트 5432를 사용해요. 다른 PostgreSQL 인스턴스에 연결하거나 포트를 바꿨다면 연결 문자열을 그에 맞게 업데이트해요. 컨테이너 이름(postgres-dev)은 Docker DNS에 의해 네트워크상의 컨테이너 IP 주소로 해석돼요.

호스트에서 연결하기 (외부 접근)

psql, pgAdmin, DBeaver 같은 도구나 데이터베이스 관리 스크립트로 호스트 머신에서 PostgreSQL에 연결하려면 PostgreSQL의 포트(5432)를 호스트에 게시해야 해요. 이렇게 하면 외부 도구가 PostgreSQL 컨테이너에 도달할 수 있어요.

localhost에만 Postgres 노출하기 (개발에 권장)

이것은 127.0.0.1에 바인딩되므로 네트워크의 다른 장치가 아닌 로컬 머신에서만 접근할 수 있어요. 개발에 가장 안전한 옵션이에요.

docker run -d --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 127.0.0.1:5432:5432 \
  -v postgres_data:/var/lib/postgresql \
  postgres:18

이제 호스트에서 연결해요:

  • 호스트: localhost 또는 127.0.0.1
  • 포트: 5432

호스트에 psql이 설치되어 있다면:

psql -h localhost -p 5432 -U postgres

비밀번호를 묻는 메시지가 나와요. 또는 PGPASSWORD 환경 변수를 사용할 수 있어요:

PGPASSWORD=mysecretpassword psql -h localhost -p 5432 -U postgres

PostgreSQL GUI 도구로 연결하기

인기 있는 PostgreSQL GUI 도구는 다음 공통 연결 정보로 연결할 수 있어요: 호스트: localhost, 포트: 5432, 사용자: postgres, 데이터베이스: postgres(또는 당신의 데이터베이스 이름).

  • pgAdmin: 웹 기반 PostgreSQL 관리·개발 플랫폼
  • DBeaver: PostgreSQL과 많은 다른 데이터베이스를 지원하는 범용 데이터베이스 도구. 연결 유형을 PostgreSQL로 선택하세요
  • TablePlus: macOS와 Windows용 세련된 인터페이스를 갖춘 현대적 네이티브 데이터베이스 관리 도구

모든 도구가 POSTGRES_PASSWORD로 설정한 비밀번호를 물어볼 거예요.

모든 네트워크 인터페이스에 Postgres 노출하기 (주의해서 사용)

네트워크의 다른 장치에서 연결을 허용하려면 -p 127.0.0.1:5432:5432 대신 -p 5432:5432를 사용해요. 이것은 PostgreSQL을 호스트의 모든 네트워크 인터페이스에 바인딩해, 호스트에 도달할 수 있는 어떤 장치에서든 접근 가능하게 만들어요. localhost뿐만 아니라요.

docker run -d --name postgres-dev \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql \
  postgres:18

Warning

PostgreSQL을 모든 네트워크 인터페이스(0.0.0.0:5432)에 노출하면 호스트에 도달할 수 있는 어떤 장치에서든 접근 가능하게 돼요. 신뢰할 수 있는 네트워크 환경이나 방화벽 뒤에서만 사용하세요. 프로덕션에서는 리버스 프록시나 VPN 사용을 고려하세요.

외부 접근을 위한 PostgreSQL 보안 고려 사항

PostgreSQL을 외부 접근에 노출할 때 다음 PostgreSQL 특유의 보안 관행을 따르세요:

  • postgres 슈퍼유저 사용 피하기: 기본 postgres 사용자는 전체 데이터베이스 권한을 가져요. 어플리케이션이 필요한 권한만 가진 전용 사용자를 만들어요.
  • 강력한 비밀번호 사용: PostgreSQL 비밀번호는 복잡해야 해요. 비밀번호를 하드코딩하는 대신 환경 변수나 시크릿 관리 사용을 고려해요.
  • 네트워크 노출 제한: 127.0.0.1(localhost만)에 바인딩하는 것이 모든 인터페이스(0.0.0.0)에 노출하는 것보다 더 안전해요.
  • SSL/TLS 고려: 프로덕션에서는 PostgreSQL이 SSL 연결을 요구하도록 구성해요. Advanced Configuration and Initialization 가이드가 PostgreSQL 설정 구성 방법을 보여줘요.
  • 어플리케이션별 사용자 만들기: 초기화 스크립트를 사용해 제한된 권한의 사용자를 만들어요. 예를 들어 보고용 읽기 전용 사용자나 특정 데이터베이스에만 접근할 수 있는 사용자가 있어요.

Advanced configuration and initialization 가이드는 초기화 스크립트로 사용자와 역할을 자동 생성하는 방법을 보여줘요.

네트워킹에 Docker Compose 사용하기

Docker Compose는 서비스용 네트워크를 자동으로 만들어 네트워킹 구성을 더 간단하게 만들어요. 다음은 내부와 외부 접근을 모두 결합한 예제예요:

services:
  db:
    image: postgres:18
    container_name: postgres-dev
    environment:
      POSTGRES_PASSWORD: mysecretpassword
    volumes:
      - postgres_data:/var/lib/postgresql
    ports:
      - "127.0.0.1:5432:5432"  # Expose to localhost only
    networks:
      - app-network

  app:
    build: ./my-app
    environment:
      DATABASE_URL: postgresql://postgres:mysecretpassword@db:5432/mydb
    networks:
      - app-network
    depends_on:
      - db

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

이 PostgreSQL 중심 구성에서:

  • app 서비스는 연결 문자열에서 서비스 이름(db)을 호스트네임으로 사용해 PostgreSQL에 연결해요
  • PostgreSQL은 외부 도구용으로 호스트의 localhost:5432에서 접근 가능해요
  • 두 서비스 모두 커스텀 네트워크에서 격리되어 네트워크 수준 보안을 제공해요
  • depends_on 지시자는 PostgreSQL이 어플리케이션보다 먼저 시작되도록 보장해요

app 서비스용 PostgreSQL 연결 정보:

  • 호스트네임: db (Docker DNS에 의해 해석)
  • 포트: 5432 (PostgreSQL 기본 포트)
  • 데이터베이스: mydb (연결 문자열에 지정됨)
  • 사용자: postgres (또는 만든 커스텀 사용자)

Note

Docker Compose는 프로젝트용 네트워크를 자동으로 만들어요. 명시적인 네트워크 구성 없이도 서비스 이름으로 서로에게 도달할 수 있지만, 커스텀 네트워크를 정의하면 더 많은 제어를 얻을 수 있어요. PostgreSQL의 경우, 컨테이너 재시작이나 IP 변경과 관계없이 어플리케이션이 항상 서비스 이름으로 연결할 수 있다는 뜻이에요.

문제 해결 (Troubleshooting)

이 섹션은 Docker 네트워킹 작업 시 흔한 PostgreSQL 연결 문제와 해결책을 다뤄요.

"Could not translate host name postgres-dev"

  • 두 컨테이너가 같은 Docker 네트워크(my-app-net)에 있어야 해요.
  • 네트워크가 존재하는지 확인해요: docker network ls
  • 컨테이너가 어떤 네트워크에 있는지 확인해요: docker inspect postgres-dev | grep NetworkMode
  • 기본 브리지 네트워크가 아닌 사용자 정의 네트워크를 사용하고 있는지 확인해요

"Connection refused" 또는 "could not connect to server"

  • PostgreSQL이 아직 초기화 중일 수 있어요: PostgreSQL이 시작되고 데이터베이스 클러스터를 초기화하는 데 몇 초 걸려요. 컨테이너 시작 후 5-10초 기다렸다가 다시 시도해요.

  • PostgreSQL 컨테이너가 실행 중인지 확인해요:

docker ps --filter name=postgres-dev
  • 초기화나 연결 오류에 대해 PostgreSQL 로그를 확인해요:
docker logs postgres-dev

"database system is ready to accept connections" 같은 메시지를 찾아 PostgreSQL이 완전히 시작됐는지 확인해요.

  • 포트 매핑이 올바른지 확인해요:
docker port postgres-dev

이것은 5432/tcp -> 127.0.0.1:5432(또는 모든 인터페이스에 바인딩했다면 0.0.0.0:5432)를 보여줘야 해요.

  • 컨테이너 안에서 PostgreSQL 연결성을 테스트해요:
docker exec -it postgres-dev psql -U postgres -c "SELECT version();"

이것이 동작하는데 외부 연결이 실패한다면, 문제는 PostgreSQL 자체가 아니라 포트 게시에 있는 거예요.

"Password authentication failed" 또는 "FATAL: password authentication failed for user"

  • 비밀번호 확인: 컨테이너를 시작할 때 POSTGRES_PASSWORD로 설정한 것과 같은 비밀번호를 사용하는지 확인해요.
  • 기존 볼륨의 이전 자격 증명: 기존 볼륨을 재사용했다면 원래 초기화 때의 비밀번호가 여전히 적용돼요. POSTGRES_PASSWORD 환경 변수는 첫 데이터베이스 초기화 때만 비밀번호를 설정해요. 재설정하려면:
    • 볼륨 제거: docker volume rm postgres_data
    • 또는 이전 비밀번호로 연결
    • 또는 연결 후 비밀번호 변경: ALTER USER postgres WITH PASSWORD 'newpassword';
  • 비밀번호 프롬프트로 연결 시도: psql -h localhost -U postgres -W (-W 플래그는 비밀번호 프롬프트를 강제해요)
  • PGPASSWORD 환경 변수 사용: PGPASSWORD=mysecretpassword psql -h localhost -U postgres
  • PostgreSQL 인증 구성 확인: pg_hba.conf를 커스터마이즈했다면 인증 방법이 비밀번호 인증을 허용하는지 확인해요

"Network not found"

  • 컨테이너를 시작하기 전에 네트워크가 존재하는지 확인해요: docker network create my-app-net
  • Docker Compose를 사용한다면 docker compose up을 실행할 때 네트워크가 자동으로 생성돼요

PostgreSQL 동반 도구 (Companions for PostgreSQL)

PostgreSQL 생태계 동반 도구: pgAdmin, PgBouncer, 성능 테스트

독립형 PostgreSQL 컨테이너를 실행하는 것은 종종 시작에 불과해요. 수천 개의 연결이 도착하거나 데이터베이스를 관리할 시각적 인터페이스가 필요하면 어떻게 될까요?

이때 동반 도구(companion tools) 가 등장해요. 이 어플리케이션들은 핵심 데이터베이스 엔진이 기본으로 제공하지 않는 기능으로 PostgreSQL을 확장해요: 시각적 관리, 연결 풀링, 성능 벤치마킹이죠. 이 가이드는 Docker에서 pgAdmin 4, PgBouncer, Pgpool-II, pgbench를 배포하는 방법, 각 도구를 언제 사용할지, 그리고 성능 영향을 보여주는 실제 벤치마크 결과를 다뤄요.

pgAdmin 4: 시각적 관리 플랫폼

pgAdmin 4는 PostgreSQL용 업계 표준 오픈소스 관리 도구예요. Docker에 배포하면 일반적으로 Server Mode로 실행되어, 하나 이상의 데이터베이스 인스턴스를 관리할 수 있는 다중 사용자 웹 인터페이스를 제공해요.

psql로 명령줄에서 모든 것을 할 수 있지만, 시각적 인터페이스는 복잡한 쿼리 작성, 테이블 구조 시각화, 데이터베이스 객체 탐색을 크게 단순화해요.

주요 고려 사항

Docker에서 pgAdmin을 실행할 때 다음 점을 유의하세요:

  • 이미지: 공식 dpage/pgadmin4 이미지 사용
  • 네트워킹: Docker Compose 환경에서 pgAdmin은 localhost가 아닌 내부 서비스 이름(예: db:5432)으로 데이터베이스에 연결해요

Docker Compose 구성

pgAdmin을 빠르게 배포하려면:

pgadmin:
  image: dpage/pgadmin4:8.14
  environment:
    PGADMIN_DEFAULT_EMAIL: [email protected]
    PGADMIN_DEFAULT_PASSWORD: secure_password
  volumes:
    - pgadmin_data:/var/lib/pgadmin
  ports:
    - "8080:80"

이 구성으로 http://localhost:8080에서 pgAdmin 인터페이스에 접근해요. 초기 로그인에는 환경 변수에 지정된 이메일과 비밀번호를 사용해요.

Important

프로덕션 환경에서는 PGADMIN_DEFAULT_PASSWORD를 외부 환경 변수로 전달하거나 Docker secrets을 사용해요. docker-compose.yml 안에 비밀번호를 평문으로 저장하는 것은 보안 위험이에요.

이제 시각적 데이터베이스 관리를 갖췄으니, 프로덕션 환경의 다음 과제는 연결 부하를 처리하는 거예요. 다음 섹션은 대용량 데이터베이스 트래픽을 관리하는 방법을 설명해요.

PgBouncer: 가벼운 연결 풀링

PostgreSQL은 모든 클라이언트 연결마다 새 프로세스를 만들어 상당한 RAM을 소비해요. 1,000명의 동시 사용자가 있으면 어떻게 될까요? PgBouncer가 정확히 이 문제를 해결해요.

PgBouncer는 연결을 풀링하는 가벼운 프록시로, 수천 개의 어플리케이션이 소수의 실제 데이터베이스 백엔드를 공유할 수 있게 해줘요. 교통 통제자처럼 생각하면 돼요: 모두가 동시에 통과하려 하지만, 통제자가 흐름을 조절해 혼잡을 방지해요.

풀링 모드 (Pooling modes)

PgBouncer는 세 가지 뚜렷한 풀링 모드를 제공해요:

모드 설명 사용 사례
Session 세션 전체 기간 동안 연결 할당 수명이 긴 연결, 세션 변수
Transaction 각 트랜잭션이 끝나면 연결 반환 웹 어플리케이션, 마이크로서비스 (가장 흔함)
Statement 모든 SQL 문장 후 연결 반환 단순 쿼리, 다중 문장 트랜잭션 없음

PgBouncer를 언제 사용할까

다음을 만나면 PgBouncer가 필수적이 돼요:

  • "too many connections" 오류
  • 연결 오버헤드로 인한 높은 메모리 소비
  • 많은 수명이 짧은 연결 (웹 어플리케이션, 서버리스 함수)
  • 제한된 데이터베이스 연결로 수천 명의 클라이언트를 서빙해야 함

완전한 Docker Compose 구성

PostgreSQL과 PgBouncer를 함께 실행하려면 docker-compose.yml, pgbouncer.ini, userlist.txt 세 파일이 필요해요.

먼저 PgBouncer 구성 파일(pgbouncer.ini)을 만들어요:

[databases]
benchmark = host=postgres port=5432 dbname=benchmark user=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = trust
auth_file = /etc/pgbouncer/userlist.txt
admin_users = postgres
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
max_db_connections = 100

다음으로 사용자 인증 파일(userlist.txt)을 만들어요:

"postgres" "postgres"

마지막으로 Docker Compose 파일(docker-compose.yml)을 만들어요:

services:
  postgres:
    image: postgres:18
    container_name: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: benchmark
      POSTGRES_HOST_AUTH_METHOD: trust
    volumes:
      - postgres_data:/var/lib/postgresql
    ports:
      - "5432:5432"
    networks:
      - pgnet
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  pgbouncer:
    image: percona/percona-pgbouncer:1.25.0
    container_name: pgbouncer
    volumes:
      - ./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini
      - ./userlist.txt:/etc/pgbouncer/userlist.txt
    ports:
      - "6432:6432"
    networks:
      - pgnet
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:

networks:
  pgnet:
    driver: bridge

주요 구성 메모:

  • PgBouncer는 5432 포트의 직접 PostgreSQL 연결과의 혼동을 피하기 위해 6432 포트에서 수신 대기해요
  • service_healthy 조건의 depends_on 지시자는 PostgreSQL이 준비된 후에만 PgBouncer가 시작되도록 보장해요
  • pool_mode = transaction은 대부분의 웹 어플리케이션에 최적의 선택이에요
  • Percona PgBouncer 이미지는 마운트된 구성 파일을 요구해요(:ro 플래그 없이 — entrypoint 스크립트가 수정해야 하기 때문)
  • 이 예제는 단순함을 위해 trust 인증을 사용해요. 프로덕션에서는 적절한 SCRAM-SHA-256 인증을 구성하세요

Note

Percona PgBouncer entrypoint 스크립트는 시작 시 구성 파일을 처리해요. 권한 오류를 피하려면 읽기 전용 플래그 없이 마운트하세요.

pgbench: 성능 벤치마킹

pgbench는 공식 PostgreSQL 이미지에 포함된 벤치마킹 유틸리티예요. 무거운 워크로드를 시뮬레이션하고 Docker 구성이 압력 아래에서 어떻게 동작하는지 확인할 수 있게 해줘요.

벤치마크 테이블 초기화

먼저 테스트 테이블을 만들어요. -s(scale) 파라미터는 데이터 크기를 결정해요 — 스케일 팩터 50은 약 5백만 행을 만들어요:

docker exec postgres pgbench -i -s 50 -U postgres benchmark

스트레스 테스트 실행

주요 파라미터:

  • -c: 시뮬레이션된 클라이언트 수
  • -j: 스레드 수
  • -T: 초 단위 기간

직접 PostgreSQL 연결로 테스트:

docker exec postgres pgbench -h localhost -U postgres -c 50 -j 4 -T 60 benchmark

PgBouncer를 통한 테스트:

docker exec postgres pgbench -h pgbouncer -p 6432 -U postgres -c 50 -j 4 -T 60 benchmark

벤치마크 결과 이해하기

PgBouncer가 실제로 차이를 만드나요? 직접 벤치마크를 실행해 확인해보세요. 결과는 하드웨어, Docker 구성, 네트워크 설정, 시스템 부하에 따라 달라져요.

무엇을 기대할까

이 벤치마크를 실행하면 특정 숫자보다는 패턴을 관찰하게 돼요. 직장으로 가는 두 가지 다른 경로를 비교하는 것처럼 생각하세요: "더 빠른" 경로는 교통 상황, 시간대, 당신의 차량에 달려 있어요.

주요 관찰 사항

직접 연결과 PgBouncer를 비교하면 일반적으로 다음을 알아차릴 수 있어요:

1. 연결 오버헤드가 크게 다르다

직접 연결은 PostgreSQL이 각 클라이언트마다 새 프로세스를 만들어야 해요. PgBouncer는 기존 연결을 재사용해요. 결과에서 "initial connection time" 메트릭을 주목하세요 — PgBouncer는 종종 극적으로 더 빠른 연결 설정을 보여줘요.

2. 압력 아래의 동작이 진짜 차이를 드러낸다

클라이언트 수(-c 파라미터)를 점차 늘려보세요: 50, 100, 150, 200. 어떤 시점에서 직접 연결은 "too many clients already"로 실패하지만 PgBouncer는 요청을 계속 처리해요. 이것이 PgBouncer의 핵심 가치예요: 연결 고갈을 방지한다는 것.

3. 처리량은 환경에 따라 다르다

일부 시스템에서는 직접 연결이 낮은 동시성에서 더 높은 초당 트랜잭션(TPS)을 보여줘요. 다른 시스템에서는 클라이언트가 적어도 PgBouncer가 이겨요. 차이는 다음에 달려 있어요:

  • 사용 가능한 CPU와 메모리
  • Docker 네트워킹 오버헤드
  • 디스크 I/O 속도
  • 연결이 빠르게 열리고 닫히는지 여부

더 알아보기 (Learn more)