Traveling Tech Guy
Traveling Tech Guy

Reputation: 27849

How to run Sequelize migrations inside Docker

I'm trying to docerize my NodeJS API together with a MySQL image. Before the initial run, I want to run Sequelize migrations and seeds to have the tables up and ready to be served.

Here's my docker-compose.yaml:

version: '3.8'
services: 
  mysqldb:
    image: mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_USER: myuser
      MYSQL_ROOT_PASSWORD: mypassword
      MYSQL_DATABASE: mydb
    ports:
      - '3306:3306'
    networks:
      - app-connect
    volumes: 
      - db-config:/etc/mysql
      - db-data:/var/lib/mysql
      - ./db/backup/files/:/data_backup/data
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: node-mysql-app
    depends_on:
      - mysqldb
    ports:
      - '3030:3030'
    networks:
      - app-connect
    stdin_open: true
    tty: true
volumes: 
  db-config:
  db-data:
networks:
  app-connect:
      driver: bridge

Here's my app's Dockerfile:

FROM node:lts-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3030
ENV PORT 3030
ENV NODE_ENV docker
RUN npm run db:migrate:up
RUN npm run db:seeds:up
CMD [ "npm", "start" ]

And here's my default.db.json that the Sequelize migration uses (shortened):

{
  "development": {
    
  },
  "production": {
    
  },
  "docker": {
    "username": "myuser",
    "password": "mypassword",
    "database": "mydb",
    "host": "mysqldb",
    "port": "3306",
    "dialect": "mysql"
  }
}

Upon running compose up the DB installs well, the image deploys, but when it reaches the RUN npm run db:migrate:up (which translates into npx sequelize-cli db:migrate) I get the error:

npx: installed 81 in 13.108s

Sequelize CLI [Node: 14.17.0, CLI: 6.2.0, ORM: 6.6.2]

Loaded configuration file "default.db.json".
Using environment "docker".


ERROR: getaddrinfo EAI_AGAIN mysqldb
npm ERR! code ELIFECYCLE
npm ERR! errno 1

If I change the "host" in the default.db.json to "127.0.0.1", I get ERROR: connect ECONNREFUSED 127.0.0.1:3306 in place of the ERROR: getaddrinfo EAI_AGAIN mysqldb.

What am i doing wrong, and what host should I specify so the app can see the MySQL container? Should I remove the network? Should I change ports? (I tried combinations of both to no avail, so far).

Upvotes: 7

Views: 12233

Answers (5)

amdev
amdev

Reputation: 7460

Late to the party but if you stuck with this even in 2024, here is the solution:

# Dockerfile

FROM node:22-alpine
WORKDIR /usr/app

RUN npm i -g pnpm

COPY ./ ./
RUN pnpm i

RUN chmod +x ./scripts/your-service-entry.sh

ENTRYPOINT [ "./scripts/your-service-entry.sh" ]

# This is a temp command to run in dev mode
# in principle we might have separate docker files for dev and prod
CMD [ "pnpm", "dev" ] 

And here is the content of your-service-entry.sh

#!/bin/sh

echo ">>>> Setup DB Migration before container starts <<<<"

echo $PG_HOST
echo $PG_PORT
echo $PG_USER
echo $PG_PASS
echo $PG_DB_NAME

# Construct the DATABASE_URL
export DATABASE_URL="postgres://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/${PG_DB_NAME}"

# Output the DATABASE_URL to verify
echo "DATABASE_URL=${DATABASE_URL}"

npx node-pg-migrate up

echo ">>>> DB migrations work done <<<<"

# make sure everything after this entry point script will run
exec "$@"

make sure to run:

chmod +x ./scripts/your-service-entry.sh

Upvotes: 1

Nihar Patel
Nihar Patel

Reputation: 21

The most straightforward method is to execute the migration using your script.

"scripts": {
    "start": "npm run migrate-db && node index.js"
  }

Upvotes: 1

mosabbir tuhin
mosabbir tuhin

Reputation: 625

The following configuration worked for me, I am adding the .env, sequelize configuration along with mysql database and docker. And finally don't forget to run docker-compose up --build cheers 🎁 🎁 🎁

.env

DB_NAME="testdb"
DB_USER="root"
DB_PASS="root"
DB_HOST="mysql"

.sequelizerc now we can use config.js rather than config.json for sequelize

const path = require('path');

module.exports = {
  'config': path.resolve('config', 'config.js')
}

config.js

require("dotenv").config();

module.exports = {
  development: {
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    dialect: "mysql"
  },
  test: {
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    dialect: "mysql"
  },
  production: {
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME,
    host: process.env.DB_HOST,
    dialect: "mysql"
  }
}

database-connection with sequelize

import Sequelize from 'sequelize';
import dbConfig from './config/config';

const conf = dbConfig.development;

const sequelize = new Sequelize(
  conf.database,
  conf.username,
  conf.password,
  {
    host: conf.host,
    dialect: "mysql",
    operatorsAliases: 0,
    logging: 0
  }
);

sequelize.sync();

(async () => {
  try {
    await sequelize.authenticate();
    console.log("Database connection setup successfully!");
  } catch (error) {
    console.log("Unable to connect to the database", error);
  }
})();

export default sequelize;
global.sequelize = sequelize;

docker-compose.yaml

version: "3.8"

networks:
  proxy:
    name: proxy

services:
  mysql:
    image: mysql
    networks:
      - proxy
    ports:
      - 3306:3306
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=testdb
    healthcheck:
      test: "mysql -uroot -p$$MYSQL_ROOT_PASSWORD  -e 'SHOW databases'"
      interval: 10s
      retries: 3
  api:
    build: ./node-backend
    networks:
      - proxy
    ports:
      - 3000:3000
    depends_on:
      mysql:
        condition: service_healthy

Dockerfile

FROM node:16

WORKDIR /api
COPY . /api
RUN npm i
EXPOSE 3000
RUN chmod +x startup.sh
RUN npm i -g sequelize-cli
RUN npm i -g nodemon

ENTRYPOINT [ "./startup.sh" ]

startup.sh

#!/bin/bash

npm run migrate-db
npm run start

Upvotes: 4

Emre
Emre

Reputation: 370

I found a really clean solution wanted to share it. First of all I used docker-compose, so if you are using only docker, it might not help. First thig first, I created a docker file which looks like that.I am using typescript, so if you are using js, you don't need to download typescript and build it!

FROM node:current-alpine
WORKDIR /app
COPY . ./
COPY .env.development ./.env


RUN npm install
RUN npm install -g typescript

RUN npm install -g sequelize-cli
RUN npm install -g nodemon

RUN npm run build
RUN rm -f .npmrc

RUN cp -R res/ dist/
RUN chmod 755 docker/entrypoint.sh
EXPOSE 8000

EXPOSE 3000
EXPOSE 9229
CMD ["sh", "-c","--","echo 'started';while true; do sleep 1000; done"]

Till here it is standart. In order to do things in right order, I need a docker compose and entrypoint file. Entrypoint file is a file, that runs when your containers start. Here is docker-compose file.

version: '3'

services:
  app:
    build:
      context: ..
      dockerfile: docker/Dockerfile.development
    entrypoint: docker/development-entrypoint.sh
    ports:
      - 3000:3000
    env_file:
      - ../.env.development
    depends_on:
      - postgres
      
  
  postgres:
    image: postgres:alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=test
    volumes:
      - ./docker_postgres_init.sql:/docker-entrypoint-initdb.d/docker_postgres_init.sql

As you can see, I am using postgresql for db. My docker file, docker-compose and also entrypoint files are in a folder called docker, thats why the paths starts wtih docker, change it according to your file structure. Last and the best part is the entrypoint file. It is really simple.

#!/bin/sh

echo "Starting get ready!!!"
sequelize db:migrate
nodemon ./dist/index.js

Ofcourse change the path of the index.js file according to your settings. Hope it helps!

Upvotes: 1

Traveling Tech Guy
Traveling Tech Guy

Reputation: 27849

I solved my issue by using Docker Compose Wait. Essentially, it adds a wait loop that samples the DB container, and only when it's up, runs migrations and seeds the DB.

My next problem was: those seeds ran every time the container was run - I solved that by instead running a script that runs the seeds, and touchs a semaphore file. If the file exists already, it skips the seeds.

Upvotes: 2

Related Questions