gizarmaluke
gizarmaluke

Reputation: 55

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:

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!

Upvotes: 3

Views: 10537

Answers (2)

mirsahib
mirsahib

Reputation: 445

I might be late for the conversation but the following step works for me

docker-compose.yml

    version: "3.7"
    services:
        db:
            #same as OP
        web:
            #same as OP
            
    migration:
        build:
          context: .
        command:
          [
            "./wait-for-it/wait-for-it.sh",
            "db:5432",
            "--",
            "npm",
            "run",
            "migrate",
            "up"
          ]
        links:
          - db
        depends_on:
          - db
        environment:
        - DATABASE_URL=postgres://username:password@host:5432/dbname

Dockerfile

    FROM docker_service
    ## optional 
    #RUN apk update && apk add git
    #RUN /bin/sh -c "apk add --no-cache bash"
    
    ENV NODE_ENV TEST
    WORKDIR /usr/src/app
    RUN npm install --only=dev
    RUN git clone https://github.com/vishnubob/wait-for-it.git
    EXPOSE 1337
    CMD ["npm", "run", "test"]

Add "migrate": "node-pg-migrate" in script section of package.json

Reference:

https://javascript.plainenglish.io/run-migrations-using-docker-in-node-and-pg-c6d80e7cd578

https://github.com/vishnubob/wait-for-it

Upvotes: 1

Stefan Golubović
Stefan Golubović

Reputation: 1275

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

Upvotes: 2

Related Questions