65

I am using docker-compose to deploy a multicontainer python Flask web application. I'm having difficulty understanding how to create tables in the postgresql database during the build so I don't have to add them manually with psql.

My docker-compose.yml file is:

web:
  restart: always
  build: ./web
  expose:
    - "8000"
  links:
    - postgres:postgres
  volumes:
    - /usr/src/flask-app/static
  env_file: .env
  command: /usr/local/bin/gunicorn -w 2 -b :8000 app:app

nginx:
  restart: always
  build: ./nginx/
  ports:
    - "80:80"
  volumes:
    - /www/static
  volumes_from:
    - web
  links:
    - web:web

data:
  restart: always
  image: postgres:latest
  volumes:
    - /var/lib/postgresql
  command: "true"

postgres:
  restart: always
  image: postgres:latest
  volumes_from:
    - data
  ports:
    - "5432:5432"

I dont want to have to enter psql in order to type in:

CREATE DATABASE my_database;
CREATE USER this_user WITH PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE "my_database" to this_user;
\i create_tables.sql

I would appreciate guidance on how to create the tables.

Daerdemandt
  • 2,281
  • 18
  • 19
blahblahblah
  • 2,299
  • 8
  • 45
  • 60

3 Answers3

65

It didn't work for me with the COPY approach in Dockerfile. But I managed to run my init.sql file by adding the following to docker-compose.yml:

    volumes:
        - ./init.sql:/docker-entrypoint-initdb.d/init.sql

init.sql was in the same directory as my docker-compose.yml. I picked the solution from this gist. Check this article for more information.

Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
Victor Di
  • 988
  • 10
  • 16
57

I dont want to have to enter psql in order to type in

You can simply use container's built-in init mechanism:

COPY init.sql /docker-entrypoint-initdb.d/10-init.sql

This makes sure that your sql is executed after DB server is properly booted up.

Take a look at their entrypoint script. It does some preparations to start psql correctly and looks into /docker-entrypoint-initdb.d/ directory for files ending in .sh, .sql and .sql.gz.

10- in filename is because files are processed in ASCII order. You can name your other init files like 20-create-tables.sql and 30-seed-tables.sql.gz for example and be sure that they are processed in order you need.

Also note that invoking command does not specify the database. Keep that in mind if you are, say, migrating to docker-compose and your existing .sql files don't specify DB either.

Your files will be processed at container's first start instead of build stage though. Since Docker Compose stops images and then resumes them, there's almost no difference, but if it's crucial for you to init the DB at build stage I suggest still using built-in init method by calling /docker-entrypoint.sh from your dockerfile and then cleaning up at /docker-entrypoint-initdb.d/ directory.

Haralan Dobrev
  • 7,617
  • 2
  • 48
  • 66
Daerdemandt
  • 2,281
  • 18
  • 19
14

I would create the tables as part of the build process. Create a new Dockerfile in a new directory ./database/

FROM postgres:latest
COPY . /fixtures
WORKDIR /fixtures
RUN /fixtures/setup.sh

./database/setup.sh would look something like this:

#!/bin/bash
set -e

/etc/init.d/postgresql start
psql -f create_fixtures.sql    
/etc/init.d/postgresql stop

Put your create user, create database, create table sql (and any other fixture data) into a create_fixtures.sql file in the ./database/ directory.

and finally your postgres service will change to use build:

postgres:
    build: ./database/
    ...

Note: Sometimes you'll need a sleep 5 (or even better a script to poll and wait for postgresql to start) after the /etc/init.d/postgresql start line. In my experience either the init script or the psql client handles this for you, but I know that's not the case with mysql, so I thought I'd call it out.

Homunculus Reticulli
  • 65,167
  • 81
  • 216
  • 341
dnephin
  • 25,944
  • 9
  • 55
  • 45
  • Thanks for the help. I've run into this error when building the docker-compose.yml. `bin/sh: 1: /fixtures/setup.sh: Permission denied Service 'postgres' failed to build: The command '/bin/sh -c /fixtures/setup.sh' returned a non-zero code: 126` – blahblahblah Oct 26 '15 at 15:30
  • Ok, you can add `set -x` to the top of the script as well, so it will print out each step, and you can figure out where it's failing. – dnephin Oct 26 '15 at 16:55
  • 3
    I added `RUN chmod +x /fixtures/setup.sh` which resolved that problem. I now get `No PostgreSQL clusters exist; see "man pg_createcluster" ... (warning). psql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"? Service 'postgres' failed to build: The command '/bin/sh -c /fixtures/setup.sh' returned a non-zero code: 2` – blahblahblah Oct 26 '15 at 19:06
  • Ok, I think you're hitting the case I describe in the "Note" at the bottom of the answer – dnephin Oct 26 '15 at 19:47
  • Did you ever get around this cluster issue? – GreenGodot Nov 07 '15 at 11:26
  • This sounds a lot like provisioning. Wouldn't it be better to use ad-hoc tools for that, e.g. Ansible? – asymmetric Jan 04 '16 at 16:00
  • Do you mean to `COPY` and `WORKDIR` the `database` directory? What is the `fixtures/` directory for? – dopatraman May 27 '17 at 01:34
  • No, it's not the database directory. `fixtures` is a directory which contains `.sql` files which can be loaded into the database. – dnephin May 27 '17 at 16:12