359

I have been trying to set up a container for a development postgres instance by creating a custom user & database. I am using the official postgres docker image. In the documentation it instructs you to insert a bash script inside of the /docker-entrypoint-initdb.d/ folder to set up the database with any custom parameters.

My bash script: make_db.sh

su postgres -c "createuser -w -d -r -s docker"
su postgres -c "createdb -O docker docker"

Dockerfile

FROM library/postgres

RUN ["mkdir", "/docker-entrypoint-initdb.d"]
ADD make_db.sh /docker-entrypoint-initdb.d/

The error I get from the docker logs -f db (db is my container name) is:

createuser: could not connect to database postgres: could not connect to server: No such file or directory

It seems that the commands inside of the /docker-entrypoint-initdb.d/ folder are being executed before postgres is started. My question is, how do I set up a user/database programmatically using the official postgres container? Is there any way to do this with a script?

Soviut
  • 88,194
  • 49
  • 192
  • 260
pech0rin
  • 4,588
  • 3
  • 18
  • 22

9 Answers9

604

EDIT - since Jul 23, 2015

The official postgres docker image will run .sql scripts found in the /docker-entrypoint-initdb.d/ folder.

So all you need is to create the following sql script:

init.sql

CREATE USER docker;
CREATE DATABASE docker;
GRANT ALL PRIVILEGES ON DATABASE docker TO docker;

and add it in your Dockerfile:

Dockerfile

FROM library/postgres
COPY init.sql /docker-entrypoint-initdb.d/

But since July 8th, 2015, if all you need is to create a user and database, it is easier to just make use to the POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB environment variables:

docker run -e POSTGRES_USER=docker -e POSTGRES_PASSWORD=docker -e POSTGRES_DB=docker library/postgres

or with a Dockerfile:

FROM library/postgres
ENV POSTGRES_USER docker
ENV POSTGRES_PASSWORD docker
ENV POSTGRES_DB docker

for images older than Jul 23, 2015

From the documentation of the postgres Docker image, it is said that

[...] it will source any *.sh script found in that directory [/docker-entrypoint-initdb.d] to do further initialization before starting the service

What's important here is "before starting the service". This means your script make_db.sh will be executed before the postgres service would be started, hence the error message "could not connect to database postgres".

After that there is another useful piece of information:

If you need to execute SQL commands as part of your initialization, the use of Postgres single user mode is highly recommended.

Agreed this can be a bit mysterious at the first look. What it says is that your initialization script should start the postgres service in single mode before doing its actions. So you could change your make_db.ksh script as follows and it should get you closer to what you want:

NOTE, this has changed recently in the following commit. This will work with the latest change:

export PGUSER=postgres
psql <<- EOSQL
    CREATE USER docker;
    CREATE DATABASE docker;
    GRANT ALL PRIVILEGES ON DATABASE docker TO docker;
EOSQL

Previously, the use of --single mode was required:

gosu postgres postgres --single <<- EOSQL
    CREATE USER docker;
    CREATE DATABASE docker;
    GRANT ALL PRIVILEGES ON DATABASE docker TO docker;
EOSQL
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
Thomasleveil
  • 95,867
  • 15
  • 119
  • 113
  • Worked perfectly. Thank you. I guess I just didn't understand the purpose of single user mode before. Interesting feature! – pech0rin Oct 28 '14 at 12:44
  • Is there a way to run an sql file in single user mode – DarVar Jan 06 '15 at 17:05
  • 2
    you can with: `gosu postgres postgres --single < /tmp/somefile.sql` – Thomasleveil Jan 06 '15 at 17:06
  • 2
    How can I run `psql -U myDb -d myDb -f myDb.sql` – DarVar Jan 06 '15 at 17:38
  • 3
    Note, --single is no longer supported in any of the postgres Dockerfiles. – brianz Aug 25 '15 at 23:02
  • 1
    I followed the steps and when I ssh into the mysql container and cd into `docker-entrypoint-initdb.d` I see my `init.sql` with the sql code in it. BUT when I open mysql (using root access) and type show databases I see only the default on from the `docker-compose.yml` file! Why it's not working for me? – Mahmoud Zalt Oct 31 '16 at 23:22
  • inside the Dockerfile, you can prevent creating multiple cache layers by chaining all of the `ENV` commands together, like `ENV POSTGRES_USER=docker POSTGRES_PASSWORD=docker POSTGRES_DB=docker` – Mark Chackerian Apr 10 '17 at 20:48
  • 1
    Using the `ENV` variables creates a superuser of the database (like the default `postgres` superuser). I want to create a user for my service which is not a superuser, using the script works, but how do I set the user's password? – Pitt Dec 14 '17 at 06:03
  • try `CREATE USER "$POSTGRES_USER" PASSWORD $pass ;` See https://www.postgresql.org/docs/8.0/static/sql-createuser.html – Thomasleveil Dec 14 '17 at 07:49
  • 2
    Its not executing `db.sql` even after it is copied to `docker-entrypoint-initdb.d\` – kamal Mar 06 '18 at 12:10
  • Simple note to otherwise very valuable and helpful answer: On Windows, I had troubles with the .sh script in `/docker-entrypoint-initdb.d` directory. *The script must not contain `\r` character.* I made it working after concatenating commands using `;` character. (In my case the script was not too long - only mvn calls to initialize db via flyway.) – Tomáš Záluský Apr 05 '18 at 18:20
  • Just to add to this. `.sh` scripts also run when put under `/docker-entrypoint-initdb.d` – Krimson Apr 10 '18 at 02:46
  • What if there are more than 1 file in `docker-entrypoint.initdb.d`? – kamal Dec 19 '18 at 12:51
  • 1
    @kamal, they all get loaded, see the entrypoint script: https://github.com/docker-library/postgres/blob/3f585c58df93e93b730c09a13e8904b96fa20c58/11/docker-entrypoint.sh#L132 – Thomasleveil Dec 19 '18 at 13:04
  • Does this cause the database to be deleted between deployments? I find that my db is blown away every time I scale to zero then back to n pods. – Jason Leach Oct 06 '19 at 16:47
  • How do I execute multiple SQL files present in `/docker-entrypoint-initdb.d/` folder in a specific sequence? – Underoos Oct 15 '19 at 08:11
  • 1
    files in `/docker-entrypoint-initdb.d/` are taken in alphabetical order. Just prefix your file names with numbers : `001-create-tables.sql`, `002-insert-data.sql`; ... – Thomasleveil Oct 15 '19 at 14:40
  • Won't this try to re-create existing databases and objects every time the service is launched --and work only the first time? Also, what about trying to do this when you're using `docker secrets` with `docker-compose`? – code_dredd Oct 29 '19 at 02:18
  • no, only when the volume holding Postgres data is empty. – Thomasleveil Oct 29 '19 at 08:33
  • 6
    Although this is marked as the correct answer, there is a nuance here nobody talked about: POSTGRES_USER/PASSWORD will set the _super user_ in your database. That may not always be a good idea to use for your main database user. Instead consider creating a separate _application_ user & database. – Stefan Arentz Apr 18 '20 at 14:48
  • This did not work for me. I'm still getting the error "psql: error: could not connect to server: could not connect to server: Connection refused" – Rich Apodaca Jul 17 '21 at 15:50
  • 8
    As per September 2021, this can be seen on PostgreSQL official page on docker: **Warning**: scripts in `/docker-entrypoint-initdb.d` are only run if you start the container with a data directory _that is empty_. – LucasBr Sep 01 '21 at 16:25
  • @LucasBr exactly! I'm having issues with the Postgres image not recreating the user specified by the env vars in a production environment that uses a persistent volume. For persistent here I mean that every time I restart the container, I don't wipe out the volumes but instead I just do a down and then an up -d. And this is written basically nowhere – Giacomo Cerquone Sep 07 '21 at 22:22
  • How do you get these to execute as PSQL though? Instead of only SQL? Similarly, doesn't this blow away the DB every time you restart the container...? – Douglas Gaskell Feb 11 '23 at 19:14
50

By using docker-compose:

Assuming that you have following directory layout:

$MYAPP_ROOT/docker-compose.yml
           /Docker/init.sql
           /Docker/db.Dockerfile

File: docker-compose.yml

version: "3.3"
services:
  db:
    build:
      context: ./Docker
      dockerfile: db.Dockerfile
    volumes:
      - ./var/pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

File: Docker/init.sql

CREATE USER myUser;

CREATE DATABASE myApp_dev;
GRANT ALL PRIVILEGES ON DATABASE myApp_dev TO myUser;

CREATE DATABASE myApp_test;
GRANT ALL PRIVILEGES ON DATABASE myApp_test TO myUser;

File: Docker/db.Dockerfile

FROM postgres:11.5-alpine
COPY init.sql /docker-entrypoint-initdb.d/

Composing and starting services:

docker-compose -f docker-compose.yml up --no-start
docker-compose -f docker-compose.yml start
Vlad
  • 6,402
  • 1
  • 60
  • 74
35

With docker compose there's a simple alternative (no need to create a Dockerfile). Just create a init-database.sh:

#!/bin/bash
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE USER docker;
    CREATE DATABASE my_project_development;
    GRANT ALL PRIVILEGES ON DATABASE my_project_development TO docker;
    CREATE DATABASE my_project_test;
    GRANT ALL PRIVILEGES ON DATABASE my_project_test TO docker;
EOSQL

And reference it in the volumes section:

version: '3.4'

services:
  postgres:
    image: postgres
    restart: unless-stopped
    volumes:
      - postgres:/var/lib/postgresql/data
      - ./init-database.sh:/docker-entrypoint-initdb.d/init-database.sh
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    ports:
      - 5432:5432

volumes:
  postgres:
danigb
  • 711
  • 6
  • 13
  • This didn't work for me. It appears the volumes directive creates a directory, not a file, in docker-entrypoint-initdb.d. The errors is that the file is a directory. – Rich Apodaca Jul 17 '21 at 15:45
  • 2
    Confirmed it works perfect on my local machine. Just one thing we need to make local `./init-database.sh` executable by running `chmod 755 ./init-datbase.sh`. – Ron Feb 20 '22 at 12:24
  • docker creates an empty directory if the source file is not found... for me that means usually i messed up the paths... – A. Rabus Mar 23 '22 at 14:20
  • 1
    Sorry, bit of a noob question - in docker-compose you specify `POSTGRES_PASSWORD` for the container environment. Why is this not used in `init-database.sh` (whilst `POSTGRES_USER` is)? – ron_g Jul 06 '22 at 13:00
  • where do you put chmod 755 inside the docker-compose file? – PirateApp Aug 24 '22 at 09:46
25

You can use this commands:

docker exec -it yournamecontainer psql -U postgres -c "CREATE DATABASE mydatabase ENCODING 'LATIN1' TEMPLATE template0 LC_COLLATE 'C' LC_CTYPE 'C';"

docker exec -it yournamecontainer psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE postgres TO postgres;"
Gabriel
  • 511
  • 6
  • 8
  • 12
    `ENCODING 'LATIN1'` is very weird... You must have very particular needs to avoid using utf8 – Zyigh Oct 22 '19 at 20:38
17

You can now put .sql files inside the init directory:

From the docs

If you would like to do additional initialization in an image derived from this one, add one or more *.sql or *.sh scripts under /docker-entrypoint-initdb.d (creating the directory if necessary). After the entrypoint calls initdb to create the default postgres user and database, it will run any *.sql files and source any *.sh scripts found in that directory to do further initialization before starting the service.

So copying your .sql file in will work.

m0meni
  • 16,006
  • 16
  • 82
  • 141
  • environmental variables for user/password and db creation still works. (9.5.3) – Jeremiah Adams Aug 17 '16 at 20:52
  • 1
    I have a project that works locally. However, when I move it to AWS EC2, it says that DB is not found. I copy the .sql file in the proper directory, but it still returns that error. Any idea where I could look? I am new to Docker. – MadPhysicist Nov 17 '16 at 22:53
  • 1
    This is only true if there is a single db instance bound to a single volume, i.e. it won't run any scripts if anything is already in the volume folder. This gives me a headache, because I want to have a separate db initializers for each of my stack services. – Violet Red Sep 25 '17 at 09:49
  • 8
    From the [docs](https://github.com/docker-library/docs/tree/master/postgres#initialization-scripts) `Warning: scripts in /docker-entrypoint-initdb.d are only run if you start the container with a data directory that is empty; any pre-existing database will be left untouched on container startup. One common problem is that if one of your /docker-entrypoint-initdb.d scripts fails (which will cause the entrypoint script to exit) and your orchestrator restarts the container with the already initialized data directory, it will not continue on with your scripts.` – sh87 Jun 30 '20 at 22:41
  • What is the order in which sql files are applied in case we have multiple? – mrpandey May 15 '23 at 20:55
10

This is my docker-compose.yml

   postgres:
    image: postgres:latest
    ports:
      - 5432:5432
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ${PROJECTDIR}/init.sql:/docker-entrypoint-initdb.d/1-schema.sql
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
      - POSTGRES_PASSWORD=postgres

Here are two things you need to consider:

  1. docker-entrypoint-initdb.d will only work when your data dictionary is empty. So if you have volumes, be sure to delete them docker-compose down --volumes.

  2. Create your user and database in the init.sql instead of docke-compose.yml. In docker-compose.yml, you should specify the postgres user .

firelynx
  • 30,616
  • 9
  • 91
  • 101
haojie
  • 593
  • 1
  • 7
  • 19
9

With Postgres latest image (ID: b262c8b2fb54) and Docker version 20.10.6 the docker-compose will look like,

version: '2'

services:
  app:
    # the detail of app
  db:
    image: 'postgres'
    container_name: book-shop-db-postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=bookshop-db

This will create the user, databases as mentioned while starting

Abhishek Chatterjee
  • 1,962
  • 2
  • 23
  • 31
8

I add custom commands to a environment evoked in a CMD after starting services... I haven't done it with postgres, but with Oracle:

#set up var with noop command
RUN export POST_START_CMDS=":"
RUN mkdir /scripts
ADD script.sql /scripts
CMD service oracle-xe start; $POST_START_CMDS; tail -f /var/log/dmesg

and start with

docker run -d ... -e POST_START_CMDS="su - oracle -c 'sqlplus @/scripts/script' " <image>

.

Rondo
  • 3,458
  • 28
  • 26
4

You need to have the database running before you create the users. For this you need multiple processes. You can either start postgres in a subshell (&) in the shell script, or use a tool like supervisord to run postgres and then run any initialization scripts.

A guide to supervisord and docker https://docs.docker.com/articles/using_supervisord/

seanmcl
  • 9,740
  • 3
  • 39
  • 45
  • Or just `RUN service postgresql start ; su - postgres -c "psql -c \"CREATE USER test WITH PASSWORD 'test' CREATEDB\"" ; service postgresql stop` – mrm Mar 04 '21 at 04:31