SQL & Database — Thực chiến PostgreSQL/Cài Postgres local — psql + GUI tool trong 15 phút
~15 phútNền tảng relational lượt xem

Cài Postgres local — psql + GUI tool trong 15 phút

3 cách cài (Docker recommend, Postgres.app, brew). Apply TaskFlow schema. Tour psql + DBeaver/TablePlus/pgAdmin. Sẵn sàng query bài đầu tiên.

Ba bài trước giải thích tại sao cần database, relational model là gì, và cách vẽ ER diagram trước khi gõ DDL. Bây giờ cần Postgres thực sự chạy trên máy để gõ SELECT đầu tiên — lý thuyết không thay được thực hành trên data thật.

Bài này 15 phút: chọn cách cài phù hợp, khởi động Postgres, kết nối bằng psql, mở GUI tool, và apply schema TaskFlow. Cuối bài bạn có psql sẵn sàng, 10 user trong bảng users, và hiểu 8 lệnh psql thiết yếu nhất.

1. Analogy — Setup môi trường dev vs đặt đồ ăn

Cài Postgres local giống chọn cách chuẩn bị bữa ăn — cùng kết quả cuối cùng (có đồ ăn), nhưng mức độ kiểm soát và công sức khác nhau.

Cách chuẩn bịCách cài Postgres
Gọi combo set sẵn từ nhà hàngDocker Compose — config sẵn, chạy một lệnh
Mua nguyên liệu siêu thị, tự nấuPostgres.app (macOS) — cài binary native, GUI built-in
Trồng rau, nuôi gà, nấu từ đầubrew/apt install — full control mọi thứ
Combo nhanh nhưng khó tùy chỉnhDocker: port, volume, version đều config được qua YAML
Siêu thị chỉ có ở một thành phốPostgres.app chỉ chạy trên macOS
Nuôi gà cần nhiều bước nhấtbrew/apt cần tự tạo cluster, config auth, tạo database
💡 Cách nhớ

Docker = combo set sẵn: nhanh, reproducible, dọn sạch một lệnh. Chọn Docker nếu bạn đang setup dev environment mới — đặc biệt khi OLHub repo đã có sẵn docker-compose.yml.

2. 3 cách cài — chọn cách nào

CáchƯu điểmNhược điểmKhi nào dùng
Docker Compose (recommend)Reproducible, dọn sạch dễ, OLHub repo có sẵn configCần Docker Desktop installed, thêm port mappingMọi dev mới, mọi OS
Postgres.app (macOS)Native binary, GUI built-in, cài dễChỉ macOS, không reproducible giữa máyCasual dev macOS muốn nhanh
brew/apt installNative, control phiên bản, không cần DockerPhải tự config, dễ conflict với Postgres hệ thốngPower user quen CLI

Bài này hướng dẫn chi tiết cả 3 cách. Làm theo một cách, bỏ qua hai cách còn lại.

3. Cài Docker Compose (recommend) — từng bước

Yêu cầu: Docker Desktop đã cài và đang chạy.

Bước 1 — Chuẩn bị docker-compose.yml.

Nếu bạn đã clone OLHub repo, file này đã có sẵn. Nếu chưa, tạo file docker-compose.yml với nội dung tối thiểu:

services:
  postgres:
    image: postgres:16-alpine
    ports:
      - "5433:5432"
    environment:
      POSTGRES_DB: olhub
      POSTGRES_USER: olhub
      POSTGRES_PASSWORD: olhub_secret_2026
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Bước 2 — Khởi động Postgres.

docker compose up -d postgres

Verify đang chạy:

docker compose ps postgres

Output mong đợi:

NAME             IMAGE                COMMAND                  SERVICE    CREATED         STATUS                   PORTS
olhub-postgres   postgres:16-alpine   "docker-entrypoint.s…"   postgres   5 seconds ago   Up 4 seconds (healthy)   0.0.0.0:5433->5432/tcp

Bước 3 — Kết nối bằng psql.

docker compose exec postgres psql -U olhub -d olhub

Output mong đợi:

psql (16.x)
Type "help" for help.

olhub=#

Prompt olhub=# nghĩa là bạn đã kết nối vào database olhub với quyền superuser. Gõ \q để thoát.

Connection string từ host (dùng khi kết nối GUI tool hoặc app):

postgresql://olhub:olhub_secret_2026@localhost:5433/olhub

Lưu ý port 5433 — OLHub map host port 5433 vào container port 5432 để tránh conflict với Postgres khác đang chạy trên máy.

Pitfall — port 5432 already in use

Nếu thấy lỗi Error starting userland proxy: listen tcp 0.0.0.0:5433: bind: address already in use, có process khác đang dùng port đó. Kiểm tra: lsof -i :5433. Giải pháp: (a) stop process kia, (b) đổi host port trong docker-compose.yml từ 5433 sang port khác, ví dụ 5434. Nếu port 5432 bị chiếm (Postgres local khác), OLHub đã dùng 5433 để tránh xung đột — đây là lý do thiết kế đó.

Dọn sạch khi không cần:

docker compose down          # stop container, giu data
docker compose down -v       # stop container + xoa volume (mat data)

4. Cài Postgres.app (macOS) — alternative

Dành cho: macOS dev muốn native binary, không muốn Docker overhead.

Bước 1 — Download và cài.

Vào postgresapp.com, download file .dmg, mở và kéo Postgres.app vào thư mục Applications.

Bước 2 — Khởi động và config.

Mở Postgres.app từ Applications. Icon xuất hiện trên menu bar. Click "Initialize" để tạo cluster lần đầu. Click "Start" để start server.

Mặc định: port 5432, không password (trust mode), username = OS username (ví dụ alice).

Bước 3 — Kết nối bằng psql.

Postgres.app bundle psql binary riêng — không dùng lệnh psql hệ thống. Mở bằng:

/Applications/Postgres.app/Contents/Versions/latest/bin/psql -d postgres

Hoặc click đúp vào tên database trong cửa sổ Postgres.app để mở psql thẳng.

Pitfall — lệnh psql không có trong PATH

Sau khi cài Postgres.app, gõ psql trong terminal báo command not found. Lý do: Postgres.app không tự thêm binary vào PATH. Fix theo hướng dẫn trong app: thêm dòng sau vào ~/.zshrc hoặc ~/.bashrc:

export PATH="/Applications/Postgres.app/Contents/Versions/latest/bin:$PATH"

Sau đó source ~/.zshrc và gõ lại psql.

5. Cài qua brew (macOS) / apt (Linux) — power user

macOS với Homebrew:

brew install postgresql@16
brew services start postgresql@16

Tạo database mới:

createdb taskflow
psql -d taskflow

Linux Ubuntu/Debian:

sudo apt update
sudo apt install -y postgresql-16 postgresql-contrib
sudo systemctl start postgresql
sudo systemctl enable postgresql

Kết nối qua socket với user hệ thống postgres:

sudo -u postgres psql
Pitfall — brew không tự tạo database

Sau brew services start postgresql@16, chạy psql thẳng báo lỗi database "username" does not exist. Brew tạo cluster nhưng không tạo database tên user. Phải chạy createdb ten_database riêng, ví dụ createdb taskflow. Linux apt thì có sẵn database postgres với user postgres — kết nối qua sudo -u postgres psql.

6. GUI tool — chọn cái nào

ToolPlatformMiễn phí?Điểm mạnhKhi nào dùng
DBeaver CommunityCross-platformMiễn phíUniversal multi-DB, ER diagram, SQL editor mạnhPower user, dùng nhiều loại database
TablePlusmacOS/WindowsFreemium (xem giá hiện tại tại tableplus.com)Native, nhanh, giao diện đẹpmacOS dev, không tiếc tiền bản quyền
pgAdmin 4Cross-platformMiễn phí, chính thứcFull PG features, monitoring, query explainCần admin chuyên sâu, đọc query plan
psql (CLI)Cross-platformMiễn phí, có sẵnScript automation, không cần UIQuen CLI, viết migration, shell scripting

Recommend cho bài này: DBeaver Community — miễn phí hoàn toàn, đủ tính năng cho toàn khoá học, chạy trên mọi OS.

Kết nối DBeaver với OLHub Docker:

Mở DBeaver → New Database Connection → PostgreSQL. Nhập:

  • Host: localhost
  • Port: 5433
  • Database: olhub
  • Username: olhub
  • Password: olhub_secret_2026

Click "Test Connection" — báo "Connected" là xong.

7. psql essentials — 8 lệnh phải nhớ

LệnhMô tả
\lList tất cả database trong cluster
\c <db>Connect sang database khác
\dtList table trong schema hiện tại (public)
\d <table>Describe table: columns, types, indexes, constraints
\duList user/role trong cluster
\timing onBật hiển thị thời gian thực thi mỗi query
\xToggle expanded display — mỗi column xuống dòng riêng, dễ đọc khi nhiều column
\qQuit psql

Demo nhanh — thử ngay sau khi kết nối:

olhub=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |
-----------+----------+----------+-------------+-------------+
 olhub     | olhub    | UTF8     | en_US.utf8  | en_US.utf8  |
 postgres  | postgres | UTF8     | en_US.utf8  | en_US.utf8  |

olhub=# \dt
Did not find any relations.

olhub=# \q

\dt trả về "Did not find any relations" vì database olhub chưa có table nào — bước tiếp theo sẽ fix điều này.

8. Apply TaskFlow schema

Schema TaskFlow gồm 5 bảng — DDL đầy đủ tour ở bài M01.5 (tour TaskFlow schema) của khoá này (section 3–7). Bài này chỉ cần copy-paste vào psql để DB sẵn sàng cho M02 trở đi.

Bước 1 — Tạo database taskflow:

docker compose exec postgres psql -U olhub -d olhub -c "CREATE DATABASE taskflow;"

Bước 2 — Apply schema DDL (paste 5 CREATE TABLE từ bài M01.5 vào psql, hoặc chạy heredoc):

docker compose exec -T postgres psql -U olhub -d taskflow <<'EOF'
CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE projects (
  id BIGSERIAL PRIMARY KEY,
  owner_id BIGINT NOT NULL REFERENCES users(id),
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE project_members (
  user_id BIGINT NOT NULL REFERENCES users(id),
  project_id BIGINT NOT NULL REFERENCES projects(id),
  role TEXT NOT NULL CHECK (role IN ('owner', 'member', 'viewer')),
  joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (user_id, project_id)
);

CREATE TABLE tasks (
  id BIGSERIAL PRIMARY KEY,
  project_id BIGINT NOT NULL REFERENCES projects(id),
  assignee_id BIGINT REFERENCES users(id),
  title TEXT NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('todo', 'doing', 'done', 'archived')) DEFAULT 'todo',
  due_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE comments (
  id BIGSERIAL PRIMARY KEY,
  task_id BIGINT NOT NULL REFERENCES tasks(id),
  user_id BIGINT NOT NULL REFERENCES users(id),
  body TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
EOF

Bước 3 — Load seed nhỏ (10 user + 5 project — đủ cho M02–M03):

docker compose exec -T postgres psql -U olhub -d taskflow <<'EOF'
INSERT INTO users (email, name) VALUES
  ('[email protected]', 'Nguyen Van An'),
  ('[email protected]', 'Tran Thi Binh'),
  ('[email protected]', 'Le Hoang Cuong'),
  ('[email protected]', 'Pham Tien Dung'),
  ('[email protected]', 'Hoang Thi Em'),
  ('[email protected]', 'Vo Minh Fan'),
  ('[email protected]', 'Bui Quoc Giang'),
  ('[email protected]', 'Do Thi Huong'),
  ('[email protected]', 'Mai Van Inh'),
  ('[email protected]', 'Ngo Hoang Khoa');

INSERT INTO projects (owner_id, name) VALUES
  (1, 'OLHub Platform'),
  (1, 'Marketing Site'),
  (2, 'Mobile App'),
  (3, 'Internal Dashboard'),
  (4, 'Data Pipeline');
EOF

Verify thành công:

docker compose exec postgres psql -U olhub -d taskflow -c "SELECT count(*) FROM users;"

Output mong đợi:

 count
-------
    10
(1 row)

10 user trong bảng users — schema và seed data đã load đúng.

Seed chỉ populate users + projects

Seed nhỏ chỉ insert 10 user và 5 project để bài M02–M03 query nhẹ. Các bảng project_members, tasks, comments còn rỗng — sẽ được sinh đầy đủ bằng script pgbench-style ở Module 7 (EXPLAIN deep) khi cần dataset lớn để bench. Nếu SELECT count(*) FROM tasks trả 0 — đó là expected.

Bài sau sẽ tour toàn bộ schema: 5 table, constraint, và mapping về ER diagram của bài M01.3.

9. Pitfall — 3 lỗi setup phổ biến

Lỗi 1 — "Connection refused"

psql: error: connection to server at "localhost" (127.0.0.1), port 5433 failed: Connection refused

Nguyên nhân: Postgres container chưa start hoặc đang khởi động. Kiểm tra: docker compose ps postgres. Nếu status không phải Up (healthy), chạy docker compose up -d postgres và đợi thêm 5-10 giây.

Lỗi 2 — "Authentication failed for user"

psql: error: connection to server failed: FATAL: password authentication failed for user "alice"

Nguyên nhân: typo password hoặc dùng sai username. OLHub dùng user olhub, không phải username hệ thống hay postgres. Kiểm tra lại connection string: postgresql://olhub:olhub_secret_2026@localhost:5433/olhub.

Pitfall — nhầm user postgres với user olhub

Postgres tạo sẵn superuser postgres khi init cluster. OLHub tạo riêng user olhub với password trong docker-compose.yml. Dùng psql -U postgres sẽ báo authentication failed vì container không có user đó (trust mode không được bật). Luôn dùng -U olhub khi kết nối vào OLHub Docker.

Lỗi 3 — Port conflict

Error response from daemon: driver failed programming external connectivity:
bind for 0.0.0.0:5433 failed: port is already allocated

Nguyên nhân: port 5433 đang bị process khác dùng. Kiểm tra: lsof -i :5433. Nếu là Docker container cũ, chạy docker compose down trước. Nếu là process khác, đổi host port trong docker-compose.yml sang 5434 hoặc port trống khác.

10. Applied — workflow dev hàng ngày

Sau khi setup xong, workflow thực tế sẽ trở thành:

Buổi sáng — start môi trường:

docker compose up -d postgres

Truy vấn nhanh trong terminal:

docker compose exec postgres psql -U olhub -d taskflow

Gõ lệnh xong, \q để thoát.

Alias tiện lợi — thêm vào ~/.zshrc hoặc ~/.bashrc:

alias psqltf='docker compose exec postgres psql -U olhub -d taskflow'

Sau đó chỉ cần gõ psqltf để vào psql taskflow.

Snapshot trước khi experiment:

docker compose exec postgres pg_dump -U olhub -d taskflow > taskflow-snapshot.sql

Để rollback về snapshot:

docker compose exec postgres psql -U olhub -d taskflow -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
docker compose exec -T postgres psql -U olhub -d taskflow < taskflow-snapshot.sql

Snapshot giúp thực hành thoải mái — xóa nhầm bảng hay data thì restore lại trong vài giây, không cần setup lại từ đầu.

11. Deep Dive — Postgres setup

📚 Deep Dive — Postgres setup

Ghi chú: Đọc PG docs Ch.18 khi cần hiểu cơ chế server. "Up and Running" Ch.1-2 cho hands-on admin nhanh. OLHub docker-compose.yml để hiểu config cụ thể của project này.

12. Tóm tắt

  • 3 cách cài: Docker Compose (recommend — reproducible, dọn sạch dễ), Postgres.app (macOS native, nhanh), brew/apt (full control, nhiều bước nhất).
  • OLHub map port 5433 → 5432 — host port 5433 tránh conflict với Postgres khác trên máy; connection string: postgresql://olhub:olhub_secret_2026@localhost:5433/olhub.
  • 3 GUI tool + 1 CLI: DBeaver (free, cross-platform, recommend), TablePlus (macOS, freemium), pgAdmin 4 (free, official, admin chuyên sâu) + psql CLI (free, bundled, scripting).
  • 8 psql lệnh thiết yếu: \l, \c, \dt, \d, \du, \timing on, \x, \q.
  • Schema TaskFlow apply qua 3 heredoc command (DDL + seed inline trong section 8) — verify với SELECT count(*) FROM users → 10 rows.
  • 3 lỗi phổ biến: "Connection refused" (Postgres chưa start), "Authentication failed" (sai user/password), port conflict (5433 occupied).
  • Bài tiếp theo: Module 1 bài 5 của khoá này sẽ tour toàn bộ TaskFlow schema — đọc DDL, hiểu constraint, và xem sample data.

13. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao OLHub map host port 5433 thay vì dùng default 5432? Nếu một dev khác trên cùng team cũng đang chạy Postgres local thì sao?

Port 5432 là port default của Postgres. Nếu dev đã cài Postgres native (brew, apt, Postgres.app) và đang chạy, port đó đã bị chiếm. Docker container cố gắng bind thêm vào port 5432 sẽ báo "port already allocated" và không start được.

OLHub dùng host port 5433 để tránh xung đột: Postgres native chạy trên 5432, OLHub Docker chạy trên 5433 — cả hai cùng tồn tại trên một máy mà không conflict.

Nếu dev khác trong team cũng chạy OLHub Docker, mỗi người chạy trên máy riêng — không có conflict vì port binding là per-machine. Conflict chỉ xảy ra khi hai process trên cùng một máy cùng bind một port.

Q2
Phân biệt khi nào dùng psql CLI và khi nào dùng GUI tool. Cho 2 task ví dụ phù hợp cho mỗi cái.

psql CLI phù hợp khi:

  • Chạy migration script tự động trong CI/CD pipeline — không cần UI, chỉ cần pipe file vào psql.
  • Viết alias hoặc shell script để query nhanh định kỳ, ví dụ kiểm tra row count sau mỗi lần import data.

GUI tool phù hợp khi:

  • Explore schema lần đầu — DBeaver hiển thị cây table, column type, FK relationship dưới dạng ER diagram, nhanh hơn nhiều lần \d liên tiếp.
  • Debug query phức tạp — pgAdmin 4 và DBeaver hiển thị query execution plan dạng đồ họa, dễ thấy đâu là bottleneck hơn là đọc text output của EXPLAIN ANALYZE.

Nguyên tắc: CLI cho automation và scripting; GUI cho exploration và debugging tương tác.

Q3
Bạn cài Postgres bằng brew và start service. Sau đó chạy `docker compose up -d postgres` trong OLHub repo thì báo lỗi port conflict. Diagnose và fix?

Brew Postgres chạy trên port 5432 (default). OLHub Docker dùng host port 5433 — nếu báo conflict trên port 5433, có nghĩa có gì đó khác đang bind port đó (hiếm gặp). Nếu conflict trên 5432, có nghĩa Docker đang cố bind port 5432 — không phải do OLHub config mà có thể do file compose khác.

Diagnose: lsof -i :5433lsof -i :5432 để xem process nào đang dùng. Nếu là brew Postgres trên 5433 (không phải mặc định — nhưng có thể bạn đã đổi config), chạy brew services stop postgresql@16.

Fix chuẩn cho OLHub: brew Postgres chạy 5432, OLHub Docker chạy 5433 — hai cái không conflict. Nếu vẫn conflict, kiểm tra file docker-compose.yml xem port mapping có đúng 5433:5432 không, và chắc chắn không có Docker container cũ nào đang giữ port.

Q4
Bạn chạy `\dt` trong psql và thấy 'Did not find any relations' dù đã CREATE TABLE trước đó. Nguyên nhân có thể là gì?

Có hai nguyên nhân phổ biến:

  • Đang ở sai database: \dt chỉ list table trong database hiện tại. Nếu CREATE TABLE chạy trong database taskflow nhưng hiện tại đang connect vào database olhub, không thấy table nào là đúng. Fix: dùng \c taskflow để switch sang database đúng, sau đó \dt lại.
  • Table ở schema khác public: \dt mặc định chỉ show table trong schema public. Nếu table được tạo trong schema khác (vd app.users), dùng \dt app.* hoặc \dt *.* để xem tất cả schema.

Kiểm tra nhanh: SELECT current_database(); để biết đang ở database nào, và SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'; để thấy mọi table trong mọi schema.

Q5
Bạn muốn rollback database về state trước khi experiment một loạt ALTER TABLE và DELETE. Lệnh nào? Tradeoff so với dùng migration tool như Flyway/Liquibase?

Snapshot và restore bằng pg_dump:

  • Snapshot trước: docker compose exec postgres pg_dump -U olhub taskflow > snapshot.sql
  • Restore: drop schema và load lại từ file snapshot.

Ưu điểm pg_dump: nhanh, không cần setup thêm tool, hoạt động với mọi Postgres instance, phù hợp cho experiment cá nhân.

Nhược điểm: thủ công, không tự động theo dõi lịch sử thay đổi, snapshot có thể bị lỗi thời nếu schema tiếp tục thay đổi sau khi tạo. Restore mất thời gian nếu data lớn.

Migration tool (Flyway, Liquibase, Prisma Migrate): mỗi thay đổi schema là một file migration có version — apply theo thứ tự, roll back theo thứ tự ngược. Ưu điểm: lịch sử rõ ràng, team cùng sync schema dễ dàng, CI/CD tự động apply. Nhược điểm: Postgres không có built-in down migration — Prisma 7 (OLHub dùng) cũng vậy; muốn rollback phải viết reverse migration thủ công.

Với môi trường học tập, pg_dump snapshot là đủ. Với production, dùng migration tool và không dựa vào pg_dump để rollback schema.

Bài tiếp theo: Tour TaskFlow schema — đọc DDL hiểu constraint

Bài này có giúp bạn hiểu bản chất không?