Search code examples
postgresqldockerdocker-composemigrationnode-pg-migrate

Run Postgres migration with Docker/Docker compose


I am pretty new to this whole subject, so excuse me if those are silly questions. I want to run unit tests in Docker containers like so:

  • Start up a Postgres container with no tables/data
  • Run the DB migration (I am using node-pg-migrate) to create all the tables
  • Populate the DB with test data
  • Start a Node container with my service
  • Run the unit tests
  • Drop the database
  • Shut down/delete all containers (except if there were errors)

I am currently struggling running the migration. I create the service image FROM my prod image and RUN npm install --only=dev. My docker-compose.yml looks like this:

version: "3.7"
services:
    db:
        image: postgres
        environment: 
            POSTGRES_PASSWORD: test_pw
            POSTGRES_USER: test_user
            POSTGRES_DB: test_db

        ports:
            - '5432:5432'

    web:
        image: docker_tests
        environment:
            DATABASE_URL: postgres://test_user:test_pw@db:5432/test_db?ssl=false
            DATABASE_SSL: "false"
            DATABASE_PW: test_pw
            DATABASE_USER: test_user
            DATABASE_NAME: test_db
            DATABASE_HOST: db
        depends_on: 
            - db
        ports:
            - '1337:1337'

And my Dockerfile:

FROM docker_service
ENV NODE_ENV TEST
WORKDIR /usr/src/app
RUN npm install --only=dev
RUN ["./node_modules/.bin/node-pg-migrate", "up"]
EXPOSE 1337
CMD ["npm", "run", "test"]

When running the composition, both containers start and I even get

No migrations to run!
Migration complete!

However, if I run tests, no tables are available. The migration was not applied to my Postgres container and when adding RUN ["printenv"] after migration it becomes clear why: the necessary DATABASE_URL is not there. I googled and found that the env-variables specified in docker-compose.yml are only available at runtime, not during buildtime. However, when i add ENV DATABASE_URL ... to my Dockerfile, of course it cannot connect to the database since the Postgres container hasn't yet started.

How do I solve the problem? One possible soltion would be to run ./node_modules/.bin/node-pg-migrate up in web as soon as both containers are up, but Docker can only have one CMD, right? And I use it to run my unit tests.

TL;DR: How do I run migrations in a Docker Postgres-container using node-pg-migrate from a Docker service-container before running unit tests?

Thanks a lot!


Solution

  • I couldn't test this, but here's the idea:

    • have a docker-entrypoint.sh script in the same folder as Dockerfile
    #!/usr/bin/env bash
    
    ./node_modules/.bin/node-pg-migrate up
    exec "$@"
    
    • change Dockerfile to use that script
    FROM docker_service
    ENV NODE_ENV TEST
    WORKDIR /usr/src/app
    RUN npm install --only=dev
    
    COPY docker-entrypoint.sh /usr/local/bin/
    RUN chmod 777 /usr/local/bin/docker-entrypoint.sh && \
        ln -s usr/local/bin/docker-entrypoint.sh / # backwards compat
    
    ENTRYPOINT ["docker-entrypoint.sh"]
    EXPOSE 1337
    CMD ["npm", "run", "test"]
    

    This way the docker-entrypoint.sh script will be executed every time you create container and will execute npm run test afterwards because of exec "$@". Every DB image has this kind of setup and I advise you to take a look at the PostgreSQL Dockerfile and entrypoint script.


    You might need something like wait-for-it to make sure the DB has started, before executing migration scripts. depends_on helps you with start order but doesn't wait for the service to be healthy.

    Resource: Control startup and shutdown order in Compose