DeployEasy
DockerCơ bản

Docker Compose không nhận .env và PostgreSQL báo password authentication failed

Phân biệt .env, env_file, environment; kiểm tra biến trong container và sửa mật khẩu PostgreSQL khi volume đã khởi tạo.

· 7 phút đọc· 1.524 từ
Mục lục bài viết

Một trong những lỗi khó chịu nhất khi deploy bằng Docker Compose là bạn đã sửa .env, nhưng container vẫn dùng giá trị cũ hoặc PostgreSQL tiếp tục báo:

password authentication failed for user "app_user"

Nguyên nhân thường không nằm ở một chỗ duy nhất. Docker Compose có nhiều cơ chế liên quan đến biến môi trường, trong khi image PostgreSQL chỉ dùng một số biến khởi tạo ở lần đầu tạo database. Ngoài ra, volume giữ dữ liệu độc lập với vòng đời container nên việc recreate container không đồng nghĩa với tạo lại mật khẩu database.

Phân biệt .env, env_file và volume PostgreSQL

1. Ba khái niệm dễ nhầm

.env dùng cho nội suy Compose

Ví dụ .env:

POSTGRES_VERSION=18
API_PORT=3000

Compose:

services:
  db:
    image: postgres:${POSTGRES_VERSION}

  api:
    ports:
      - "${API_PORT}:3000"

Compose đọc các giá trị để tạo cấu hình cuối cùng. Điều này không có nghĩa mọi biến trong .env tự động xuất hiện bên trong container.

env_file đưa biến vào container

services:
  api:
    env_file:
      - ./apps/api/.env.docker

File:

NODE_ENV=production
DATABASE_HOST=db
DATABASE_PORT=5432

Các biến này được đưa vào môi trường của container api.

environment khai báo trực tiếp cho service

services:
  api:
    environment:
      NODE_ENV: production
      DATABASE_HOST: db

Khi nhiều nguồn cùng đặt một biến, thứ tự ưu tiên có thể làm giá trị bạn nghĩ đang dùng bị ghi đè. Vì vậy, luôn kiểm tra cấu hình thực tế thay vì đoán.

2. Kiểm tra Compose đang đọc file nào

Từ đúng thư mục project:

pwd
ls -la
ls -la apps/api/.env.docker

Sau đó render cấu hình:

docker compose config

Lệnh này rất hữu ích để phát hiện:

  • Biến chưa được thay thế.
  • Đường dẫn env_file sai.
  • Port hoặc image tag không như mong đợi.
  • Giá trị placeholder như CHANGE_ME_DB_PASSWORD vẫn còn.

Lưu ý bảo mật: output có thể chứa secret. Không đăng toàn bộ lên bài viết, issue công khai hoặc ảnh chụp màn hình. Hãy che password, token và private URL.

Có thể kiểm tra một biến mà không in tất cả:

docker compose config | grep -n 'DATABASE_HOST\|DATABASE_PORT'

3. Kiểm tra biến thật sự bên trong container

Sau khi container chạy:

docker compose exec api printenv DATABASE_HOST
docker compose exec api printenv DATABASE_PORT

Hoặc:

docker compose exec api env | grep '^DATABASE_'

Không nên in DATABASE_PASSWORD trên terminal được ghi log hoặc quay màn hình. Với kiểm tra tự động, chỉ nên xác nhận biến có tồn tại:

docker compose exec api sh -lc 'test -n "$DATABASE_PASSWORD" && echo set || echo missing'

Terminal demo kiểm tra biến và PostgreSQL volume

4. Đường dẫn env_file được tính từ đâu?

Trong cấu hình thông thường, đường dẫn tương đối của env_file được giải quyết theo vị trí Compose file/project. Lỗi phổ biến:

env file .../apps/api/.env.docker not found

Cấu trúc mẫu:

project/
├── compose.yaml
└── apps/
    └── api/
        └── .env.docker

Compose:

services:
  api:
    env_file:
      - ./apps/api/.env.docker

Nếu chạy từ nơi khác hoặc dùng nhiều file -f, hãy xác nhận bằng:

docker compose -f /opt/app/compose.yaml config

Bạn cũng có thể chỉ định file nội suy:

docker compose --env-file .env.production config

.env dùng cho Compose interpolation và env_file của một service là hai cơ chế khác nhau.

5. localhost trong container không phải PostgreSQL container

Compose:

services:
  api:
    build: ./api

  db:
    image: postgres:18

Từ container api, hostname database nên là tên service:

DATABASE_HOST=db
DATABASE_PORT=5432

Không dùng:

DATABASE_HOST=localhost

Trong container api, localhost trỏ về chính container api. Nó không tự trỏ sang container db.

Kiểm tra DNS nội bộ:

docker compose exec api getent hosts db

Kiểm tra cổng:

docker compose exec api sh -lc 'nc -zv db 5432'

Image API có thể chưa cài nc; khi đó dùng healthcheck hoặc một container chẩn đoán phù hợp trong cùng network.

6. Vì sao sửa POSTGRES_PASSWORD nhưng mật khẩu cũ vẫn còn?

Ví dụ:

services:
  db:
    image: postgres:18
    environment:
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: app_db
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Các biến POSTGRES_USER, POSTGRES_PASSWORDPOSTGRES_DB chủ yếu được image dùng khi thư mục dữ liệu còn trống và quá trình initdb diễn ra lần đầu.

Sau lần đầu, volume postgres_data đã chứa database. Bạn sửa .env rồi recreate container, volume vẫn giữ user và password cũ. Đây là lý do app báo sai mật khẩu dù docker compose config đã hiển thị password mới.

7. Sửa mật khẩu mà không xóa dữ liệu

Đăng nhập bằng tài khoản quản trị đang còn hoạt động. Ví dụ từ container database:

docker compose exec db psql -U postgres -d postgres

Trong psql:

ALTER USER app_user WITH PASSWORD 'mat_khau_moi';

Sau đó cập nhật secret của ứng dụng cho khớp và recreate API:

docker compose up -d --force-recreate api

Không đưa mật khẩu thật vào lịch sử shell hoặc bài viết. Trong production, ưu tiên secret manager, Docker secrets hoặc cơ chế quản lý secret của nền tảng.

Nếu bạn không còn thông tin đăng nhập quản trị, cần dùng quy trình phục hồi phù hợp với PostgreSQL và môi trường của mình. Không nên xóa volume production để “làm lại cho nhanh”.

8. Chỉ xóa volume khi dữ liệu có thể bỏ

Trong lab hoàn toàn mới, bạn có thể khởi tạo lại database:

docker compose down

docker volume ls

Xác định đúng volume rồi mới xóa:

docker volume rm project_postgres_data

Hoặc:

docker compose down -v

Cảnh báo: -v có thể xóa volume được khai báo trong Compose và làm mất toàn bộ database. Trước khi dùng với dữ liệu quan trọng, phải backup và kiểm tra khả năng restore.

Sau khi volume trống:

docker compose up -d db
docker compose logs -f db

Bạn sẽ thấy quá trình khởi tạo mới.

9. Kiểm tra DATABASE_URL

Dạng phổ biến:

postgresql://app_user:password@db:5432/app_db

Nếu password chứa ký tự đặc biệt như @, :, /, # hoặc %, chuỗi URL có thể bị phân tích sai. Cần URL-encode thành phần username/password hoặc dùng các biến tách riêng nếu thư viện hỗ trợ.

Ví dụ Node.js với biến tách riêng:

DATABASE_HOST=db
DATABASE_PORT=5432
DATABASE_USER=app_user
DATABASE_PASSWORD=your_secret
DATABASE_NAME=app_db

Cách này dễ debug hơn một chuỗi URL dài, nhưng ứng dụng phải cấu hình để đọc đúng các biến.

Không ghi secret trực tiếp trong compose.yaml đã commit vào Git.

10. Healthcheck và thứ tự khởi động

depends_on chỉ nói về quan hệ khởi động ở mức Compose; ứng dụng vẫn nên có cơ chế retry database.

Ví dụ healthcheck:

services:
  db:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
      interval: 5s
      timeout: 5s
      retries: 10

  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy

Sau đó:

docker compose ps

Healthcheck giúp phân biệt container “đang chạy process” với database “đã sẵn sàng nhận kết nối”.

11. Quy trình chẩn đoán theo thứ tự

# 1. Render cấu hình
docker compose config

# 2. Kiểm tra container
docker compose ps

# 3. Kiểm tra biến không nhạy cảm trong API
docker compose exec api env | grep '^DATABASE_HOST\|^DATABASE_PORT\|^DATABASE_USER\|^DATABASE_NAME'

# 4. Kiểm tra DNS service
docker compose exec api getent hosts db

# 5. Xem log
docker compose logs --tail=150 api
docker compose logs --tail=150 db

# 6. Kiểm tra volume
docker volume ls

Chỉ sau khi hiểu rõ volume và dữ liệu, bạn mới quyết định đổi password bằng SQL hay khởi tạo lại database lab.

12. Checklist xác minh

  • env_file tồn tại đúng đường dẫn.
  • docker compose config không còn placeholder.
  • Biến cần thiết tồn tại trong container API.
  • Host database là db, không phải localhost trong container.
  • User, database và password giữa API/PostgreSQL khớp nhau.
  • Hiểu rằng POSTGRES_PASSWORD không đổi password của volume đã khởi tạo.
  • Không xóa volume khi chưa backup.
  • API có retry hoặc chờ database healthy.

13. Câu hỏi thường gặp

Recreate container có đọc .env mới không?

Có thể đọc cấu hình mới, nhưng database trong volume vẫn giữ trạng thái đã khởi tạo. Hai việc này độc lập.

docker compose config có an toàn để đăng công khai không?

Không nên. Nó có thể render cả secret. Chỉ trích phần không nhạy cảm và che thông tin trước khi chụp ảnh.

Tại sao kết nối từ máy host dùng localhost, còn từ API container phải dùng db?

Vì host và container có network namespace khác nhau. Tên service db được Docker DNS phân giải trong Compose network.

Nguồn tham khảo

Đọc tiếp