logicOnAbstractions
logicOnAbstractions

Reputation: 2600

Safe ways to specify postgres parameters for healthchecks in docker compose

I'm using secrets to manage username, password, and dbname with a Docker stack using Postgres as a DB. I now want to use the healthcheck feature that Docker provides.

docker-compose.yml:

x-db-secrets: &db_secrets
    - psql_user
    - psql_pass
    - psql_dbname

services:
  db:
    image: postgres:13.1
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER_FILE=/run/secrets/psql_user
      - POSTGRES_DB_FILE=/run/secrets/psql_dbname
      - POSTGRES_PASSWORD_FILE=/run/secrets/psql_pass
    secrets: *db_secrets
    healthcheck:
      test: pg_isready -U myuser -d db_prod
      interval: 10s
      timeout: 3s
      retries: 3

(... other services...)

volumes:
  postgres_data:
  static_content:
  media_content:

secrets:
  psql_user:
    external: true
  psql_pass:
    external: true
  psql_dbname:
    external: true

As can be noted in the healthcheck: section, I'm exposing the db username & the dbname with the healthcheck. My question (with some follow-up based on the answer):

Thoughts? Workaround?

Additional details:

Upvotes: 47

Views: 83580

Answers (6)

pringi
pringi

Reputation: 4682

You can use pg_isready without any username/password to check if container is 'healthy'.

bbepostgres:
  image: postgres:14.2
  environment:
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
    - POSTGRES_DB=pangea_local_dev
    - PGUSER=postgres
  healthcheck:
    test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"]
    interval: 30s
    timeout: 60s
    retries: 5
    start_period: 80s

This will do the same thing as you wanted.

Reference: https://github.com/peter-evans/docker-compose-healthcheck

Upvotes: 59

Angelo van Meurs
Angelo van Meurs

Reputation: 311

It seems like the usage of CMD-SHELL gives false negatives when not in the format of:
"{CMD} || exit 1"

After some testing I found that indeed the issue was that for pg_isready you need to set PG{KEYWORD} environment args instead of POSTGRES_{KEYWORD} args.

Nevertheless, I found pg_ready to be redundant and went for the following approach:

test: ["CMD-SHELL", "psql -U ${DB_USER} -d ${DB_MAIN} -c 'SELECT 1' || exit 1"]

This approach saves you from having to set env vars twice.

You can test that it works by replacing 'SELECT 1' with 'SELECT 1 from some_nonexistent_table'. You will see it fails after n retries.

To check the logs for the test, use the following command: (credit)

docker inspect --format "{{json .State.Health }}" ${CONTAINER_NAME}

Edit:
I would like to add that setting pg_isready username and database via args does not work. This means that passing values for -U --username and-d --database is useless and will be overwritten by the environment variables, regardless of having set those.

Upvotes: 7

Topi
Topi

Reputation: 35

I have set it in following way using docker secrets:

  db:
    container_name: db
    image: postgres:16
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      POSTGRES_USER_FILE: /run/secrets/db_user
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
      POSTGRES_DB_FILE: run/secrets/db_name
      PGUSER_FILE: /run/secrets/db_user
    secrets:
      - db_name
      - db_password
      - db_user
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -d `cat $$POSTGRES_DB_FILE` -U `cat $$PGUSER_FILE`" ]
      interval: 2s
      timeout: 5s
      retries: 10

Upvotes: 1

Eyal Solomon
Eyal Solomon

Reputation: 616

as mentioned above, you can use the pg_isready

I also added a 'dummy' container to wait for multiple container, for example postgres and mysql

version: "3.2"

services:
  postgres-db:
    image: postgres:13
    restart: always
    environment:   
      POSTGRES_PASSWORD: password   
    ports:
      - "5432:5432"
    networks:
      - default
    healthcheck:
      test: pg_isready -U postgres
    enter code here

  mysql-db:
    platform: linux/x86_64
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    healthcheck:
      test: 'mysql --user=root --password=password --execute "SHOW DATABASES;"'
    
  ready:
    image: hello-world
    depends_on:
      postgres-db:
        condition: service_completed_successfully
      mysql-db:
        condition: service_healthy

Upvotes: -1

Josh Dando
Josh Dando

Reputation: 2153

So this can be done by using a .env file and slightly modifiying your docker-compose.yml file.

POSTGRES_HOST=db
POSTGRES_USER=root
POSTGRES_PASSWORD=password
POSTGRES_DB=dev
services:
  db:
    image: postgres:13.1
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - .env
    secrets: *db_secrets
    healthcheck:
      test: ["CMD-SHELL", "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'"]
      interval: 10s
      timeout: 3s
      retries: 3

Upvotes: 49

Florentin
Florentin

Reputation: 11

Since, the docker socket is on the host, the healthcheck command is visible through docker inspect, so it does (really) matter only if the password could be retrieved from username and/or dbname.

One could say that it always matter, but it depends on your needs.

Upvotes: 1

Related Questions