pglayers เพิ่ม extension ให้ PostgreSQL ใน Docker บรรทัดเดียว ไม่ต้อง compile
pglayers แพ็ก extension ของ PostgreSQL แต่ละตัวเป็นเลเยอร์ Docker สำเร็จรูป แล้วต่อกับ image ทางการด้วย COPY --from บรรทัดเดียว ไม่ต้อง compile ใครที่เคยปวดหัวกับการลง pgvector หรือ PostGIS ใน Docker จะเห็นว่าขั้นตอนยาว ๆ เหลือแค่ก๊อปเลเยอร์มาต่อ

pglayers คือเครื่องมือที่ทำให้การเพิ่ม extension ให้ฐานข้อมูล PostgreSQL ที่รันบน Docker (แพลตฟอร์มที่ใช้รันซอฟต์แวร์แต่ละตัวแยกกันในกล่องที่เรียกว่า container) ง่ายขึ้น แค่ก๊อปบรรทัดเดียวต่อหนึ่งตัวก็เพิ่มส่วนเสริมได้ โดยไม่ต้อง compile ให้เสียเวลา extension ในที่นี้คือส่วนเสริมที่เพิ่มความสามารถให้ฐานข้อมูล เช่น pgvector ที่เก็บ embedding ไว้ทำงาน AI หรือ PostGIS ที่จัดการข้อมูลแผนที่และพิกัด ปัญหาที่คนทำงานเจอกันมานานคือ PostgreSQL เปล่า ๆ ไม่มีส่วนเสริมพวกนี้ติดมาให้ และการติดตั้งเองบน Docker ก็เป็นงานที่กินเวลาและพลาดง่าย
ใครที่เคยรัน PostgreSQL ใน Docker แล้วสั่ง CREATE EXTENSION vector; คงจำตอนที่มันขึ้น error ได้ดี เพราะ image ทางการของ PostgreSQL บน Docker ออกแบบให้เบาที่สุด จึงมาพร้อมตัวฐานข้อมูลล้วน ๆ ไม่มี extension ติดมาเลย ทางออกเดิมมีอยู่ไม่กี่ทาง ทางแรกคือลงมือ compile extension จาก source เองใน Dockerfile แล้วสู้กับ dependency ไปเรื่อย ๆ ส่วนอีกทางคือยอมใช้ image ของเจ้าอื่นที่ยัด extension ชุดตายตัวมาให้ทั้งก้อน ซึ่งเลือกเองไม่ได้ pglayers เลือกแนวทางอื่น คือทำให้ extension แต่ละตัวกลายเป็นเลเยอร์ Docker ที่หยิบมาต่อบน image ทางการได้ทีละบรรทัด เหมือนต่อเลโก้ทีละก้อนบนฐานเดิม
ทางเดิมคือ compile เองแล้วนั่งลุ้น
ตัวอย่างที่เจอกันบ่อยที่สุดคือสาย AI ที่อยากลอง pgvector เพื่อเก็บ embedding แล้วทำ RAG หรือ semantic search สั่ง CREATE EXTENSION vector; ปุ๊บก็เจอ error ทันที อยากได้ PostGIS สำหรับงานแผนที่ก็ไม่มี อยากได้ pg_cron สำหรับตั้งเวลารันงานในฐานข้อมูลก็ไม่มีอีก
พอไม่มี ก็ต้องลงเอง และการลง extension ให้ Postgres ที่รันใน container คือจุดที่หลายคนเสียเวลาทั้งบ่าย เพราะต้องเขียนขั้นตอน apt-get ลง build dependency ดึง source มา compile แล้วไล่แก้ไลบรารีที่ขาดทีละตัว พอเปลี่ยนเวอร์ชัน Postgres ทีก็มีสิทธิ์พังใหม่ทั้งชุด แล้วต้องนั่งไล่แก้กันอีกรอบ
extension กลายเป็นเลเยอร์ ต่อทีละบรรทัด

หัวใจของ pglayers อยู่ที่การมอง extension ใหม่ว่า หนึ่งตัวไม่จำเป็นต้องผ่านขั้นตอน "ติดตั้ง" แต่เป็นแค่ไฟล์ไม่กี่ไฟล์ที่ต้องวางให้ถูกที่
extension แต่ละตัวจึงมาในรูป Docker image แบบพิเศษ คือ image ที่สร้างจาก FROM scratch ไม่มีระบบปฏิบัติการ ไม่ใช่ container ที่รันได้ ข้างในมีแค่ไฟล์ .so .control และ .sql ของ extension ตัวนั้นวางอยู่ตาม path ที่ Postgres มองหา เวลาจะเอามาใช้ก็แค่เขียนบรรทัดเดียวใน Dockerfile ว่า COPY --from=ghcr.io/pglayers/pgx-<extension>:<เวอร์ชัน> / / ตอน docker build Docker จะก๊อปไฟล์เหล่านั้นใส่ใน image postgres ไปยัง path ที่ถูกต้องให้เอง เช่น /usr/lib/postgresql/17/lib/ และ /usr/share/postgresql/17/extension/
extension ไม่ได้ผ่านขั้นตอนติดตั้ง แต่ก๊อปมาวางเป็นเลเยอร์สำเร็จรูป จึงไม่มี compile เกิดขึ้นเลย
ผลที่ตามมาคือไม่ต้อง compile ไม่ต้องใช้ package manager และไม่ต้องไล่ resolve dependency ตอน build เพราะในเลเยอร์มีไบนารีที่ build มาเรียบร้อยแล้ว สิ่งที่ Dockerfile ทำมีแค่วางไฟล์ลงไปให้ถูกที่
Dockerfile จริง ก๊อปแล้วรันได้เลย

ลองดูของจริงจะเห็นภาพชัดกว่า ถ้าอยากได้ pgvector, pg_cron และ PostGIS พร้อมกัน ก็เขียน Dockerfile สามบรรทัดต่อจาก base image:
FROM postgres:17
COPY --from=ghcr.io/pglayers/pgx-pgvector:17 / /
COPY --from=ghcr.io/pglayers/pgx-pg_cron:17 / /
COPY --from=ghcr.io/pglayers/pgx-postgis:17 / /หนึ่งบรรทัดต่อหนึ่ง extension ตรงตัว จากนั้น build แล้วรัน:
docker build -t my-postgres .
docker run -d -e POSTGRES_PASSWORD=secret my-postgresเช็คว่าทุกอย่างขึ้นเรียบร้อย ดูสถานะด้วย docker ps ให้เห็นคำว่า "Up" ตามด้วย docker logs <container_id> แล้วรอจนขึ้นข้อความว่าฐานข้อมูลพร้อมรับการเชื่อมต่อแล้ว ซึ่งบรรทัดนั้นจะลงท้ายด้วย ready to accept connections จากนั้นต่อเข้าไปลองสั่งเปิด extension:
docker exec -it <container_id> psql -U postgres -c "CREATE EXTENSION vector;"คราวนี้ CREATE EXTENSION vector; ผ่านฉลุย พร้อมเก็บ embedding เพื่อทำ RAG หรือค้นหาข้อมูลที่ใกล้เคียงกันได้ทันที เท่าที่เห็นในซอร์สของโปรเจกต์ นี่คือขั้นตอนทั้งหมดตั้งแต่ต้นจนใช้งานได้จริง โดยไม่มีบรรทัด compile โผล่มาเลย
ขี้เกียจเขียน Dockerfile ก็มีทางลัด
ถ้าไม่อยากแม้แต่จะเขียน Dockerfile pglayers ก็มี image รวมสำเร็จที่ตั้งค่ามาพร้อมใช้ สั่งบรรทัดเดียวได้ Postgres ที่มี extension ครบชุด:
docker run -d -e POSTGRES_PASSWORD=secret ghcr.io/pglayers/pglayers-full:17ถ้าต้องต่อจากนอก container ให้เปิดพอร์ตเองด้วย เพราะ image นี้ไม่ได้ publish พอร์ตเป็น default:
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=secret ghcr.io/pglayers/pglayers-full:17ถ้ามีโปรแกรมอื่นใช้พอร์ต 5432 บนเครื่องอยู่แล้ว ก็ map เป็นพอร์ตอื่นได้ เช่น -p 5433:5432 โดยข้างใน container ยังใช้พอร์ต 5432 เสมอ
image รวมมีสองโปรไฟล์ให้เลือก โปรไฟล์ full ได้ extension ครบทั้ง 53 ตัว ส่วนโปรไฟล์ azure คัดมา 28 ตัวที่ตรงกับบริการ Azure Database for PostgreSQL แบบ Flexible Server ทั้งหมดทำไว้ให้ PostgreSQL 17, 18 และ 19 ทุก image เป็น multi-architecture ทั้ง amd64 และ arm64 จึงรันบน Mac รุ่น Apple Silicon ได้โดยตรง โดย Docker เลือกสถาปัตยกรรมให้เอง
ในบรรดา 53 ตัว ตัวที่คนหยิบใช้บ่อยได้แก่:
- pgvector — เก็บ embedding แล้วค้นด้วยความใกล้เคียง หัวใจของงาน RAG และ semantic search
- PostGIS — ข้อมูลพิกัด แผนที่ ระยะทาง งานภูมิศาสตร์ทั้งหลาย
- pg_cron — ตั้งเวลาให้ฐานข้อมูลรันคำสั่งเอง เช่น ลบข้อมูลเก่าทุกเที่ยงคืน
- timescaledb — ข้อมูล time-series อย่าง log, metric หรือราคาที่เปลี่ยนตามเวลา
- pg_graphql — เปิด GraphQL API จาก schema ที่มีอยู่ได้เลย
- pg_duckdb — ยิง query เชิงวิเคราะห์หนักๆ โดยดึง DuckDB มาช่วยประมวลผล
- documentdb — ทำให้ Postgres คุยโปรโตคอล MongoDB ได้ ต่อด้วย mongosh หรือ pymongo ตรงๆ
จะเลือกตัวไหนเมื่อไหร่ ก็ดูจากงานที่ทำ สาย AI เริ่มที่ pgvector งานแผนที่เริ่มที่ PostGIS งานตั้งเวลาเริ่มที่ pg_cron ส่วนที่เหลือค่อยเติมเมื่อโปรเจกต์ต้องใช้จริง ไม่ต้องลงทุกตัวตั้งแต่แรก
ไม่ใช่บรรทัดเดียวจบทุกกรณี
ถึงจะสะดวกขนาดนี้ ก็ต้องเข้าใจให้ครบว่าไม่ใช่ทุก extension จะจบที่บรรทัด COPY บรรทัดเดียว มี extension ราว 22 ตัวที่ต้องเพิ่มชื่อ extension นั้นเข้าไปใน shared_preload_libraries ของ config อีกที เช่น pg_cron, pgaudit และ timescaledb เพราะต้องโหลดตั้งแต่ตอน Postgres เริ่มทำงาน ไม่ว่าจะเป็น extension ตัวไหน ก็ต้องสั่ง CREATE EXTENSION แยกในแต่ละฐานข้อมูลที่จะใช้เสมอ ไม่ใช่ทำครั้งเดียวจบทั้ง cluster ส่วนนี้ทำให้เป็นอัตโนมัติได้ด้วยการวาง init script ไว้ที่ /docker-entrypoint-initdb.d ซึ่งจะรันครั้งเดียวตอน container เริ่มครั้งแรก
อีกจุดที่ต้องระวังคือกรณีที่ extension สองตัววางไฟล์ไว้ที่ path เดียวกัน จุดนี้โปรเจกต์ทดสอบไว้จริง ไม่ใช่แค่ทฤษฎี เพราะคำสั่ง COPY --from ตัวหลังจะเขียนทับตัวแรกแบบเงียบ ๆ โดย Docker ไม่เตือนอะไรเลย ซึ่งอาจทำให้ extension พังตอนรันจริง นี่คือเหตุผลที่โปรเจกต์รันเทสต์เช็กการชนกันของไฟล์ระหว่างทุกคู่ extension ก่อนปล่อย ล่าสุดผ่านครบ 699 เทสต์
อีกจุดที่มักเข้าใจผิดคือโปรไฟล์ azure ซึ่งทำมาเพื่อจำลองให้ใกล้เคียงกับของจริงสำหรับ dev ในเครื่องเท่านั้น ทาง README บอกไว้เองว่าเวอร์ชัน extension ค่า config และพฤติกรรมบางอย่างอาจต่างจากบริการ managed จริง จึงใช้แทน production ไม่ได้ อีกเรื่องที่ต้องระวังคือ PostgreSQL 19 ยังเป็น Experimental หรือ beta อยู่ ยังไม่ควรเรียกว่าเสถียร ส่วน 17 และ 18 นับเป็น Stable แล้ว
สุดท้าย pglayers เป็นโปรเจกต์ชุมชนที่เอา extension ของจริงจากผู้พัฒนาแต่ละเจ้ามาแพ็กใหม่ให้ต่อเป็นเลเยอร์ได้ ไม่ใช่โปรเจกต์ทางการของทีม PostgreSQL ในเอกสารของโปรเจกต์อธิบายไว้เองว่าตัวโปรเจกต์เป็นเพียงระบบช่วยประกอบอัตโนมัติที่อาศัยงานของชุมชน PostgreSQL เป็นฐานอีกที ไม่ได้มาแทนที่ของเดิม
สิ่งที่เปลี่ยนไปจริงๆ
พอการลง extension ไม่ใช่อุปสรรคอีกต่อไป คำถามในหัวก็เปลี่ยนจาก "จะลง pgvector ยังไงให้ไม่พัง" เป็น "จะเอา pgvector ไปทำอะไร" ซึ่งเป็นคำถามที่สนุกกว่า และเป็นงานจริงที่ควรเอาเวลาไปทำตั้งแต่แรก
ที่มา: โปรเจกต์ pglayers บน GitHub



