เวลาที่เราใช้ PostgreSQL ใน Docker container เราสามารถกำหนดตัวแปรสำหรับสร้าง USER, PASSWORD, DATABASE ได้เลย

ตัวอย่าง ไฟล์ docker-compose.yml

version: '3'
services:
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong
    volumes:
    - ./pg_data:/var/lib/postgresql/data

จากตัวอย่างไฟล์ docker-compose.yml ด้านบน จะได้ User / Password / Database มา 1 ชุด สามารถแสดง user ได้ด้วยคำสั่ง

docker-compose exec db psql -U kong -c '\du'

ในกรณีที่เราอยากได้  USER, PASSWORD, DATABASE มากกว่า 1 ตั้งแต่แรก เราสามารถที่จะสร้างไฟล์ สำหรับการเริ่มต้นทำงานของ PostgreSQL ใน Docker container ได้ โดยเขียนไฟล์สำหรับสร้าง USER, PASSWORD, DATABASE ไว้ที่ /docker-entrypoint-initdb.d

ตัวอย่างไฟล์ /docker-entrypoint-initdb.d/init-user-db.sh

#!/bin/sh
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE USER docker;
    CREATE DATABASE docker;
    GRANT ALL PRIVILEGES ON DATABASE docker TO docker;
EOSQL

จากนั้นแก้ Permission ของไฟล์ chmod +x init-user-db.sh และเพิ่มการแมปไฟล์เข้าไปใน container ทื่ docker-compose.yml

version: '3'
services:
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong
    volumes:
    - ./pg_data:/var/lib/postgresql/data
    - ./init-user-db.sh:/docker-entrypoint-initdb.d/init-user-db.sh

ถ้าสั่ง docker-compose up -d เพื่อรัน PostgreSQL สิ่งที่คาดว่าจะได้เพิ่มมาคือ

Database = kong,   Username = kong,  Password = kong
Database = docker, Username = docker

แต่ว่า ในกรณีนี้ จะได้แค่

Database = kong,   Username = kong,  Password = kong

เท่าเดิม...

เพราะว่า PostgreSQL มองว่ามีไฟล์ database อยู่แล้ว จะไม่รัน initdb ใหม่ ถ้าอยากได้ 2 user รวมที่อยู่ใน init-user-db.sh ด้วย ต้องลบ pg_data ทิ้ง

docker-compose down
rm -rf pg_data
docker-compose up -d
docker-compose exec db psql -U kong -c '\du'

จะเห็นว่า วิธีนี้ มันทำให้เราต้องลบ pg_data ออกก่อนถึงจะใช้ได้

ในกรณีที่ เราอยากได้ User / Password / Database เพิ่มเติม หลังจากที่รัน PostgreSQL ไปแล้ว มีหลายวิธี เช่น

  • ใช้  docker exec คำสั่ง bash/sh ก่อน จากนั้นรันคำสั่ง psql เพื่อเข้าไปใน psql shell แล้วค่อยสร้าง User / Password / Database
  • ใช้ docker exec คำสั่ง psql โดยตรง เพื่อเข้าไปสร้าง User / Password / Database
  • ใช้ docker exec คำสั่ง psql พร้อมกับส่ง sql เข้าไปให้คำสั่ง psql

หลายวิธีด้านบน สามารถทำได้ผลเหมือน ๆ กัน แต่จะใช้หลายคำสั่งในการทำกว่าจะสร้าง User / Password / Database เพิ่มได้ ผมมีเหตุการณ์ ที่อยากจะรันเพียง 1 คำสั่ง เพื่อให้สามารถสร้าง User / Password / Database สำหรับ PostgreSQL ที่รันอยู่ได้

จริง ๆ จะว่าไป มันก็ไม่ได้เป็น 1 คำสั่งซะทีเดียว แต่เป็นการเขียน script ที่มีความยืดหยุ่นมากพอ ที่จะทำให้การรัน script เพียง 1 คำสั่ง สามารถได้สิ่งที่ต้องการครบ คือการสั่ง docker exec คำสั่งในไฟล์ script

ตัวอย่างไฟล์ create-user-db.sh

#!/bin/sh
set -x
POSTGRES="psql --username ${POSTGRES_USER}"
$POSTGRES <<-SQL
CREATE USER ${DB_USER} WITH CREATEDB PASSWORD '${DB_PASSWORD}';
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};
SQL

จาก script ด้านบน เราสามารถใช้งานร่วมกันกับ docker -e ที่ใช้ตั้งค่า environment variable ได้

ตัวอย่างการใช้งาน

ต้องมี db รันอยู่ก่อนหน้านี้แล้ว

docker-compose exec -T \
  -e DB_USER=hello \
  -e DB_PASSWORD=password \
  -e DB_NAME=world \
  db sh 2>/dev/null < create-user-db.sh

จะเห็นว่า วิธีนี้ ทำให้เราไม่ต้องลบ pg_data ออกก่อน ไม่ต้อง รันคำสั่ง docker exec หลาย ๆ คำสั่ง

สำหรับ script ที่สมบูรณ์ สวยกว่าข้างบน แก้ create-user-db.sh ตามนี้

#!/bin/sh

# https://github.com/docker-library/postgres/issues/151

set -x
POSTGRES="psql --username ${POSTGRES_USER}"

echo "Before"
echo "======"
$POSTGRES <<-SQL
\du
SQL

echo -n "[*] Creating database role: ${DB_USER}... "
$POSTGRES <<-SQL
CREATE USER ${DB_USER} WITH CREATEDB PASSWORD '${DB_PASSWORD}';
SQL

echo -n "[*] Creating database ${DB_NAME}... "
$POSTGRES <<-SQL
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};
SQL

echo
echo "After"
echo "====="
$POSTGRES <<-SQL
\du
SQL

จากนั้นลองสร้าง User / Database / Password เพิ่ม

docker-compose exec -T \
  -e DB_USER=dev \
  -e DB_PASSWORD=devpass \
  -e DB_NAME=dev_db \
  db sh 2>/dev/null < create-user-db.sh