19

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):

  • Does it matter? The docker-compose.yml file doesn't live on the host where this code will run. Therefore a potential attacker wouldn't have access to this info from there... however, since docker will perform the healthcheck, it would possibly live traces in that container (shell? docker configs files where that procedure is stored?)
  • If it does matter, how can I perform the healthchecks without exposing that information? I would have to use the secrets directly, however I cannot figure out how to use the secrets in the docker-compose file...

Thoughts? Workaround?

Additional details:

  • I'm currently checking if the DB is up from the (flask) container that uses it. E.g. the flask service checks if it can ping the DB or not. However I'd rather compartmentalize things, and the DB container should be the one telling if it's healthy or not (imo).
logicOnAbstractions
  • 2,178
  • 4
  • 25
  • 37
  • Write your credentials to a [.pgpass file](https://www.postgresql.org/docs/9.1/libpq-pgpass.html) in the container. – larsks Dec 02 '20 at 20:27

4 Answers4

26

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

Aseem
  • 5,848
  • 7
  • 45
  • 69
pringi
  • 3,987
  • 5
  • 35
  • 45
  • I think this works indeed. However if more involved checks were to be performed, would it still work? At any rate, this would be an acceptable answer. I'M accepting the other one because it is more general. – logicOnAbstractions Sep 26 '22 at 14:50
  • 1
    Why is `PGUSER` needed in `environment` variables? Also why is `pg_ready` performed on `db_prod` instead of `pangea_local_dev` ? – tuk Mar 15 '23 at 16:43
  • The reason for `PGUSER` is explained [here](https://stackoverflow.com/a/60194261/785523). – tuk Mar 15 '23 at 17:16
  • Timeout duration should not be this much time. I feel it should not be more than 10s – Pravind Kumar May 17 '23 at 03:45
22

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
Josh Dando
  • 1,647
  • 14
  • 9
  • I'm accepting this answer as it is more general, e.g. allows a way to safely pass that information to the container without exposing it, including for any other script that might be run for services. Thanks a bunch. – logicOnAbstractions Sep 26 '22 at 14:50
  • I think you need to either add `-ec` instead of `-c` or add a ` || exit $?` to the end of the healthcheck test script. sh will always exit 0 otherwise, falsely reporting the service as healthy. – parity3 Sep 28 '22 at 21:26
  • @parity3 That's not true: `pg_isready` exits with status 2 when it fails to connect. Even if it did `exit 0`, the code after `||` wouldn't get called. – Zubin Oct 18 '22 at 17:26
  • You could be right, but I've moved on. I may have been confused. We'll have to leave it to others to verify. Thanks for clarifying that my comment may not be accurate. – parity3 Oct 24 '22 at 18:48
  • 1
    pg_ready is not suitable for a database-specific health-check - it returns 0 regardless of whether the database exists or credentials are OK. See: https://stackoverflow.com/questions/74095815/why-does-pg-isready-not-respect-database-option – L.M Mar 04 '23 at 19:09
  • Does the file HAVE TO be named `.env` ? Could I name it `.env.dev` or `.env.prod`? When I run `docker compose convert` the environment variables are not populated unless the file is named `.env`. Seems problematic if I want different `.env` files for dev and prod. – Jarad Mar 13 '23 at 22:14
  • Hey @Jarad, it's specified in the docker docs: https://docs.docker.com/compose/environment-variables/set-environment-variables/#substitute-with---env-file that you can substitute the env variables with a config using the CLI e.g. `docker compose --env-file ./config/.env.dev up`. I'm not sure whether you can use `env_file` attribute with different file suffixes other than `.env` but I do know you can name them like `prod.env` or `dev.env` etc. If that doesn't work atleast the CLI gives you an option which you can then tie into your CI/CD workflow. Hope that helps – Josh Dando Mar 14 '23 at 19:56
1

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.

Florentin
  • 11
  • 2
0

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
Eyal Solomon
  • 470
  • 1
  • 6
  • 15