ตั้งค่าด้วย Docker
วิธีที่เร็วที่สุดในการมี PostgreSQL จริงรันอยู่คือใช้ Docker image postgres อย่างเป็นทางการ Docker จะดาวน์โหลดฐานข้อมูลสำเร็จรูป เริ่มมันใน container ที่แยกเดี่ยว และปิดให้เรียบร้อยเมื่อคุณใช้เสร็จ — ไม่ต้องติดตั้งทั้งระบบ ไม่มี service ตกค้าง บทเรียนนี้พาคุณจากศูนย์ไปสู่ฐานข้อมูลที่รันอยู่และเชื่อมต่อได้ในไม่กี่คำสั่ง
คุณต้องมี Docker ติดตั้งและรันอยู่ ทุกอย่างด้านล่างใช้คำสั่ง docker และ docker compose ธรรมดา
ทุก transcript ในที่นี้รันบน Mac (Docker Desktop 29.6.1, Docker Compose v5.3.0, aarch64) กับ image
postgres:18.6กันยายน 2026 เพื่อไม่ชนกับสิ่งอื่นบนเครื่องนี้ การรันจริงใช้ container ชื่อpostgresql-b1-pg, named volumepostgresql-b1-pgdataและ host port55440transcript แสดงด้วยชื่อของบทเรียน (pg,pgdata,5432) ในจุดที่เป็นแค่ชื่อ ส่วน output ของdocker psปล่อยไว้ตามจริง ทุกอย่างถูกลบทิ้งหลังใช้ pgAdmin ไม่ได้ ถูกสตาร์ต (ไม่ได้ pull image) ไฟล์ compose ตรวจด้วยdocker compose configและรันเฉพาะ servicepostgres
คำสั่งเดียวเพื่อรัน Postgres
หัวข้อที่มีชื่อว่า “คำสั่งเดียวเพื่อรัน Postgres”คำสั่งเดียวนี้จะ pull image หากจำเป็นและเริ่มฐานข้อมูล พร้อมตั้งรหัสผ่าน superuser, publish port มาตรฐาน และเก็บข้อมูลไว้ใน named volume เพื่อให้ข้อมูลรอดจากการ restart container
docker run --name pg \ -e POSTGRES_PASSWORD=secret \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql \ -d postgres:18.6ทัวร์สั้น ๆ เกี่ยวกับ flag ต่าง ๆ:
--name pgตั้งชื่อที่จำง่ายให้ container เพื่อให้คำสั่งทีหลังอ้างชื่อนี้ได้-e POSTGRES_PASSWORD=secretตั้งรหัสผ่านสำหรับ userpostgresเริ่มต้น ตัวแปรนี้บังคับ ถ้าไม่ใส่ image จะไม่ยอมเริ่มทำงาน (พิสูจน์ด้านล่าง)-p 5432:5432แมป PostgreSQL port ของ container ไปยัง port เดียวกันบนเครื่องของคุณ เพื่อให้เครื่องมือบน host เชื่อมต่อกับlocalhost:5432ได้ วิธีนี้ publish บน ทุก interface ของ host ใช้-p 127.0.0.1:5432:5432เพื่อให้เข้าถึงได้จากเครื่องคุณเท่านั้น (transcript ด้านล่างทำแบบนี้)-v pgdata:/var/lib/postgresqlmount named volume หนึ่งระดับเหนือ data directory ตั้งแต่ imagepostgres:18cluster อยู่ในไดเรกทอรีย่อยที่ตั้งชื่อตาม version (18/docker) ใต้ mount นั้น จึงต้อง mount volume ที่/var/lib/postgresqlไม่ใช่/var/lib/postgresql/dataแบบบทเรียนเก่า ๆ-d postgres:18.6รัน image อย่างเป็นทางการ ปักตามรุ่นที่คอร์สนี้ตรวจสอบไว้ ในเบื้องหลัง
หน้าตาของการรันจริง พร้อมการตรวจสอบที่ส่วนถัดไปจะอธิบาย:
$ docker run --name pg -e POSTGRES_PASSWORD=secret -p 5432:5432 -v pgdata:/var/lib/postgresql -d postgres:18.6<container-id>
$ docker psNAMES IMAGE STATUS PORTSpostgresql-b1-pg postgres:18.6 Up 6 seconds 127.0.0.1:55440->5432/tcp
$ docker exec pg psql -U postgres -c "show data_directory" -c "select version()" data_directory------------------------------- /var/lib/postgresql/18/docker(1 row)
version-------------------------------------------------------------------------------------------------------------------------- PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit(1 row)
$ docker exec pg psql -U postgres -c "\conninfo" Connection Information Parameter | Value----------------------+--------------------- Database | postgres Client User | postgres Socket Directory | /var/run/postgresql Server Port | 5432 Options | Protocol Version | 3.0 Password Used | false GSSAPI Authenticated | false Backend PID | 90 SSL Connection | false Superuser | on Hot Standby | off(12 rows)บรรทัด data_directory คือหลักฐานของกฎเรื่อง volume: cluster อยู่ที่ /var/lib/postgresql/18/docker ใต้จุด mount
เกิดอะไรขึ้นกับ path mount แบบเก่า
หัวข้อที่มีชื่อว่า “เกิดอะไรขึ้นกับ path mount แบบเก่า”บทเรียนที่เขียนสำหรับ PostgreSQL 17 และเก่ากว่า mount /var/lib/postgresql/data บน image 18 container นั้นปฏิเสธที่จะเริ่ม:
$ docker run --rm -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:18.6Error: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster" (specifically, using major-version-specific directory names). This better reflects how PostgreSQL itself works, and how upgrades are to be performed.
See also https://github.com/docker-library/postgres/pull/1259
Counter to that, there appears to be PostgreSQL data in: /var/lib/postgresql/data (unused mount/volume)
This is usually the result of upgrading the Docker image without upgrading the underlying database using "pg_upgrade" (which requires both versions).
The suggested container configuration for 18+ is to place a single mount at /var/lib/postgresql which will then place PostgreSQL data in a subdirectory, allowing usage of "pg_upgrade --link" without mount point boundary issues.
See https://github.com/docker-library/postgres/issues/37 for a (long) discussion around this process, and suggestions for how to do so.exit=1ข้อความยาวแต่วิธีแก้บรรทัดเดียว: mount /var/lib/postgresql (ดู docker-library PR ที่ข้อความอ้างถึง และ เอกสาร image)
docker-compose.yml สำหรับการตั้งค่าที่ทำซ้ำได้
หัวข้อที่มีชื่อว่า “docker-compose.yml สำหรับการตั้งค่าที่ทำซ้ำได้”การพิมพ์คำสั่งนั้นทุกครั้งเริ่มน่าเบื่อ และบ่อยครั้งคุณอยากได้ GUI ควบคู่กับฐานข้อมูล docker-compose.yml เก็บการตั้งค่าทั้งหมดไว้ในไฟล์เดียว เพื่อให้เริ่มทุกอย่างด้วย docker compose up ไฟล์นี้รัน PostgreSQL พร้อม pgAdmin ซึ่งเป็น web GUI ที่คุณจะได้ทัวร์ในบทถัดไป
services: postgres: image: postgres:18.6 environment: POSTGRES_PASSWORD: secret POSTGRES_DB: appdb ports: - '5432:5432' volumes: - pgdata:/var/lib/postgresql
pgadmin: image: dpage/pgadmin4 environment: PGADMIN_DEFAULT_PASSWORD: secret ports: - '8080:80' depends_on: - postgres
volumes: pgdata:บันทึกเป็น docker-compose.yml แล้วเริ่มทุกอย่างจากไดเรกทอรีนั้น:
docker compose up -dตอนนี้ PostgreSQL อยู่ที่ localhost:5432 และ pgAdmin อยู่ที่ http://localhost:8080 ถ้าอยากได้ GUI ที่เบากว่า ให้สลับ service pgadmin เป็น image adminer ซึ่งให้หน้า admin ไฟล์เดียวบน port ของมันเอง ไม่ว่าแบบไหน service ฐานข้อมูลก็ไม่เปลี่ยน นี่คือ service ฐานข้อมูลที่รันจากไฟล์นั้น (เฉพาะ postgres และเปลี่ยน host port เป็น 55440):
$ docker compose config --quiet # the file above parses and validatesexit=0
$ docker compose up -d postgres # pgAdmin image not pulled here; only the database service Network postgresql-b1_default Creating Network postgresql-b1_default Created Volume postgresql-b1_pgdata Creating Volume postgresql-b1_pgdata Created Container postgresql-b1-postgres-1 Creating Container postgresql-b1-postgres-1 Created Container postgresql-b1-postgres-1 Starting Container postgresql-b1-postgres-1 Started
$ docker compose psSERVICE IMAGE STATUS PORTSpostgres postgres:18.6 Up 7 seconds 127.0.0.1:55440->5432/tcp
$ docker compose exec postgres psql -U postgres -d appdb -c "\l appdb" List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges-------+----------+----------+-----------------+------------+------------+--------+-----------+------------------- appdb | postgres | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |(1 row)
$ docker compose down # data stays in the pgdata volume Container postgresql-b1-postgres-1 Removed Network postgresql-b1_default Removing Volume postgresql-b1_pgdata Removing Volume postgresql-b1_pgdata Removed Network postgresql-b1_default Removedต่อด้วยข้อเรียกร้องเรื่องความคงอยู่ของข้อมูล คราวนี้ volume ถูกจัดการโดย compose down ลบ container และ network แต่เก็บ volume ไว้ ส่วน down -v ลบ volume ทิ้ง:
$ docker compose down Container postgresql-b1-postgres-1 Removed Network postgresql-b1_default Removing Network postgresql-b1_default Removed
$ docker volume ls --filter name=pgdataDRIVER VOLUME NAMElocal postgresql-b1_pgdata
$ docker compose up -d && docker compose exec postgres psql -U postgres -d appdb -c "select * from t" n--- 1(1 row)
$ docker compose down -v # -v also deletes the volume: this one is destructive Volume postgresql-b1_pgdata Removing Volume postgresql-b1_pgdata Removed Network postgresql-b1_default Removedชิ้นส่วนต่าง ๆ ประกอบกันอย่างไร
หัวข้อที่มีชื่อว่า “ชิ้นส่วนต่าง ๆ ประกอบกันอย่างไร”container คือสำเนาที่กำลังรันของ image, named volume คือที่ที่ไฟล์ข้อมูลจริงอยู่บนเครื่องของคุณ และ client เชื่อมต่อผ่าน port ที่ publish volume ถูกแยกจาก container โดยตั้งใจ เพื่อให้ข้อมูลอยู่ยาวกว่า container ใด ๆ
flowchart LR
V[("pgdata named volume")] --> P["postgres container"]
P -->|"port 5432"| H["Host machine"]
H --> CLI["psql or driver"]
H --> GUI["pgAdmin in browser"] ตรวจสอบและเชื่อมต่อ
หัวข้อที่มีชื่อว่า “ตรวจสอบและเชื่อมต่อ”ยืนยันว่า container ขึ้นแล้ว จากนั้นเชื่อมต่อ ถ้ามี psql บน host ให้ชี้ไปที่ฐานข้อมูลด้วย connection string ถ้าไม่มี psql ในเครื่อง image มีติดมาให้แล้ว:
docker pspsql "postgresql://postgres:secret@localhost:5432/postgres"docker exec -it pg psql -U postgresdocker ps แสดง image postgres:18.6 สถานะ Up และการแมป port transcript ด้านบน (ส่วนแรก) เชื่อมต่อผ่าน published port จาก container ชั่วคราวอีกตัว ซึ่งก็คือเส้นทางเดียวกับที่เครื่องมือบน host ใช้ \conninfo แสดง Server Port | 55440 และ Password Used | true ไม่ว่าแบบไหนคุณจะมาอยู่ที่ prompt postgres=# พร้อมรัน SQL
volume คือสิ่งที่พาข้อมูลไป volume เดิม container ใหม่เอี่ยม row ยังอยู่ ลบ volume แล้วก็หาย:
$ psql "postgresql://postgres:secret@localhost:5432/postgres" -c "select current_user, inet_server_port()" # through the published port (55440 here) current_user | inet_server_port--------------+------------------ postgres | 5432(1 row)
Connection Information Parameter | Value----------------------+---------------------- Database | postgres Client User | postgres Host | host.docker.internal Host Address | 192.168.65.254 Server Port | 55440 Options | Protocol Version | 3.0 Password Used | true GSSAPI Authenticated | false Backend PID | 91 SSL Connection | false Superuser | on Hot Standby | off(13 rows)
$ docker exec pg psql -U postgres -c "create table notes (id int, body text)" -c "insert into notes values (1, 'still here')"CREATE TABLEINSERT 0 1
$ docker rm -f pgpostgresql-b1-pg
$ docker run --name pg ... -v pgdata:/var/lib/postgresql -d postgres:18.6 # same volume, brand-new container<container-id>
$ docker exec pg psql -U postgres -c "select * from notes" id | body----+------------ 1 | still here(1 row)
$ docker rm -f pg && docker volume rm pgdata # now delete the volume toopostgresql-b1-pgdata$ (new container, new volume) docker exec pg psql -U postgres -c "select * from notes"ERROR: relation "notes" does not existLINE 1: select * from notes ^รหัสผ่านและ trust
หัวข้อที่มีชื่อว่า “รหัสผ่านและ trust”POSTGRES_PASSWORD บังคับ เว้นแต่คุณผ่อนการยืนยันตัวตนอย่างชัดเจน พิสูจน์แล้ว:
$ psql -h <host> -p 55441 -U postgres -w -c "select 1 as no_password_needed" # POSTGRES_HOST_AUTH_METHOD=trust no_password_needed-------------------- 1(1 row)
$ psql -h <host> -p 55442 -U postgres -w -c "select 1" # POSTGRES_PASSWORD only, no password suppliedpsql: error: connection to server at "host.docker.internal" (192.168.65.254), port 55442 failed: fe_sendauth: no password supplied
$ docker run --rm postgres:18.6 # no POSTGRES_PASSWORD at allError: Database is uninitialized and superuser password is not specified. You must specify POSTGRES_PASSWORD to a non-empty value for the superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".
You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all connections without a password. This is *not* recommended.
See PostgreSQL documentation about "trust":POSTGRES_HOST_AUTH_METHOD=trust ให้ทุกคนที่เข้าถึง port ได้เข้ามาโดยไม่ต้องใช้รหัสผ่านจริง ๆ ห้ามใช้นอกเครื่องของคุณ และห้ามใช้ร่วมกับ port ที่ publish บนทุก interface
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”เมื่อขอให้ผู้ช่วย AI เขียน “compose file ที่เก็บข้อมูลถาวร” มันให้สิ่งที่ใกล้เคียงกับที่บล็อกส่วนใหญ่ยังเขียนอยู่ อ่านดู:
🐛 หา bug ในโค้ดนี้
# docker-compose.yml (AI comment: "persist the data directory so nothing is lost")services:db: image: postgres:latest environment: POSTGRES_PASSWORD: secret ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data
volumes:pgdata:เฉลย
มีปัญหาจริงสองข้อ ทั้งสองพิสูจน์แล้วด้านบน ข้อแรก volume ถูก mount ที่ /var/lib/postgresql/data container จึง exit ด้วย status 1 พร้อมข้อความ Error: in 18+, these Docker images are configured to store database data... ไฟล์ที่ใช้ได้กับ image รุ่น 17 หรือเก่ากว่าจะพังบน image 18 ทุกตัว ข้อสอง postgres:latest ไม่ได้ปักรุ่น: ไฟล์เดิมจะหมายถึง major version อื่นแบบเงียบ ๆ ใน docker compose pull ครั้งถัดไป และ data directory ของ PostgreSQL ใช้ข้าม major ไม่ได้หากไม่มี pg_upgrade วิธีแก้คือ mount /var/lib/postgresql ปักรุ่น tag และ bind port ไว้ที่ loopback เท่านั้น:
services: db: image: postgres:18.6 environment: POSTGRES_PASSWORD: secret ports: - "127.0.0.1:5432:5432" volumes: - pgdata:/var/lib/postgresql
volumes: pgdata:Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”Image = สูตร (อ่านอย่างเดียว), container = สำเนาที่รันอยู่หนึ่งชุด (ทิ้งได้), volume = ข้อมูล (คงทน), port mapping = ประตู ปัญหาทุกอย่างในบทนี้คือหนึ่งในสี่สิ่งนี้ผิด: tag ผิด, container ตายตอนเริ่ม, volume อยู่ผิด path หรือประตูเปิดให้ทั้งเครือข่าย
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- named volume คือสิ่งที่ทำให้ข้อมูลของคุณคงอยู่ ลบ container แล้วข้อมูลยังอยู่ ลบ volume แล้วข้อมูลหายถาวร (พิสูจน์ทั้งสองอย่างด้านบน)
5432คือ port เริ่มต้นของ PostgreSQL และpostgresคือ superuser เริ่มต้น เครื่องมือต่าง ๆ สมมติค่าเหล่านี้เว้นแต่จะบอกเป็นอย่างอื่นPOSTGRES_DBสร้างฐานข้อมูลเพิ่มเมื่อเริ่มครั้งแรก (appdbด้านบน) ถ้าไม่ใส่ก็ยังมีฐานข้อมูลpostgresในตัวให้ใช้POSTGRES_PASSWORD(และPOSTGRES_DB) มีผลเฉพาะตอน data directory ว่าง การเปลี่ยนค่าแล้ว restart บน volume เดิมไม่มีผลอะไรPOSTGRES_PASSWORDบังคับสำหรับ image อย่างเป็นทางการ ใช้ secret จริงนอกการทดลองในเครื่อง ไม่ใช่secret- ปัก tag
postgres:18.6คือเป๊ะpostgres:18ตาม minor release ส่วนpostgres:latestจะกระโดดข้าม major และทำให้ volume พัง
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| รันด้วย Docker | เร็ว ทิ้งได้ และตั้งค่าเหมือนกันทุกเครื่อง | มี overhead ของ container และต่างจากการ tune บน production host จริงเล็กน้อย |
| ติดตั้ง PostgreSQL แบบ native | ใกล้เคียงสภาพแวดล้อม production จริงกว่า ไม่มีชั้นเพิ่ม | ติดตั้ง/ถอนยากกว่า และต่างกันไปตามระบบปฏิบัติการ |
| ใช้ image ดีฟอลต์ตามเดิม | ใช้งานได้ทันที ไม่ต้องเขียนอะไรเพิ่ม | ไม่ได้ tune หน่วยความจำหรือ config สำหรับ workload เฉพาะของคุณ |
| เขียน Dockerfile เอง | ปรับ config และ extension ได้ตามต้องการ | ต้องดูแล image และอัปเดตเอง |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
docker compose downลบข้อมูลของฉัน” ไม่ใช่ ที่ลบคือdown -v(หรือdocker volume rm) ทั้งสองแสดงไว้ด้านบน - “volume ต้อง mount ที่
/var/lib/postgresql/data” จริงจนถึง 17 บน image 18 mount นั้นทำให้ container exit - “
latestใช้ตอนพัฒนาได้” ใช้ได้จนกว่า tag จะย้ายไป major ใหม่ แล้ว volume เดิมจะไม่สตาร์ต - “รันด้วย superuser เริ่มต้นในเครื่องไม่เป็นไร” มันซ่อน bug เรื่องสิทธิ์ที่คุณจะเจอจริงบน production สร้าง role ที่จำกัดสิทธิ์ไว้ทดสอบ (บทถัดไป)
- “
POSTGRES_HOST_AUTH_METHOD=trustแค่สะดวก” มันเอาการยืนยันตัวตนออกสำหรับทุกคนที่เข้าถึง port ได้ (พิสูจน์ด้านบน)