DevOps

Fix Sequelize "Cannot find /config/config.json" in Docker

Reproduced in Docker: why moving WORKDIR alone still fails, why build-time migrations cannot reach the database, and a Compose setup that migrates on start.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

sequelize db:migrate resolves config/config.json relative to the directory the command runs in, not relative to the project or to node_modules. In a Dockerfile, a RUN step that comes before any WORKDIR runs in /, so the CLI looks for /config/config.json and stops. Moving WORKDIR up is not enough when COPY . . still comes after the migration step: the message just changes to /app/config/config.json. And once the config is found, a RUN migration still cannot reach a database that only exists as a Compose service. The fix that works end to end is to set WORKDIR, copy the project, and run the migration when the container starts, after the database is healthy.

The error comes from a Stack Overflow question (8 votes, about 27,000 views). Its Dockerfile installs sequelize-cli globally, runs sequelize db:migrate and sequelize db:seed:all, and only afterwards sets WORKDIR /ng-app and runs COPY . .. A Dockerfile with the same instruction order, built on 10 October 2026 with node:22-alpine, sequelize-cli 6.6.5 and Sequelize 6.37.8, fails like this:

text
#8 ERROR: process "/bin/sh -c npx sequelize-cli db:migrate" did not complete successfully: exit code: 1
2.101 Sequelize CLI [Node: 22.23.3, CLI: 6.6.5, ORM: 6.37.8]
2.129 ERROR: Cannot find "/config/config.json". Have you run "sequelize init"?

Why the CLI looks in /config#

The message is printed by getMigrator() in src/core/migrator.js, and only when two things are both false: the config file exists, and a connection URL was passed on the command line. The path it prints is built in src/helpers/config-helper.js:

js
const api = {
  // ...other members of the helper omitted
  getConfigFile() {
    if (args.config) {
      return path.resolve(process.cwd(), args.config);
    }
 
    const defaultPath = path.resolve(process.cwd(), 'config', 'config.json');
    const alternativePath = defaultPath.replace('.json', '.js');
 
    return helpers.path.existsSync(alternativePath)
      ? alternativePath
      : defaultPath;
  },
};

Everything hangs on the current working directory of the process. The CLI does not search upwards, and it does not look next to package.json or node_modules. So the absolute path in the error tells you exactly where the CLI was standing when it ran: /config/config.json means the working directory was /.

In a Dockerfile that working directory comes from WORKDIR. The Dockerfile reference says that it "sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it", and that "If not specified, the default working directory is /", while noting that a base image may already set one. With the official node:22-alpine image, a RUN placed above the first WORKDIR ran in / in the reproduction, which is why the path came out as /config/config.json.

The .sequelizerc file follows the same rule. The CLI loads it from the current directory as well (src/core/yargs.js resolves .sequelizerc against the working directory), so a .sequelizerc in the project root is ignored when the command runs from somewhere else.

Fixing the Dockerfile and the Compose file#

The question's accepted answer suggests moving WORKDIR /ng-app above the Sequelize commands. That is the right first move, but it is not the whole fix. Three things have to be true at the moment the migration runs: the working directory is the project, the project files are there, and the database answers.

  1. Set WORKDIR before anything that reads project files, and copy the project before the CLI runs. With WORKDIR moved up but COPY . . still after the migration (the accepted answer applied on its own), the build still fails, now one directory deeper:

    text
    3.919 Sequelize CLI [Node: 22.23.3, CLI: 6.6.5, ORM: 6.37.8]
    3.956 ERROR: Cannot find "/app/config/config.json". Have you run "sequelize init"?

    In the question's Dockerfile the same thing holds: the config folder only arrives with the COPY . . near the end, so the CLI would report the path under /ng-app instead.

  2. Do not run the migration as a RUN step. With WORKDIR and COPY . . both above it, the CLI now finds the file, then fails to reach the database: a build step does not run on the Compose network where the service name db resolves (reproduced here with a plain docker build):

    text
    2.104 Sequelize CLI [Node: 22.23.3, CLI: 6.6.5, ORM: 6.37.8]
    2.129 Loaded configuration file "config/config.json".
    6.583 ERROR: getaddrinfo ENOTFOUND db

    So the same RUN sequelize db:migrate line had two independent problems: where it ran, and when it ran.

  3. Run the migration when the container starts. This is the Dockerfile that was built and run in the reproduction:

    dockerfile
    FROM node:22-alpine
    WORKDIR /app
    COPY package.json ./
    RUN npm install --no-audit --no-fund
    COPY . .
    CMD ["sh", "-c", "npx sequelize-cli db:migrate && node index.js"]

    With npm install and no production flag, packages listed under devDependencies are installed, so the locally installed CLI is there. If your image installs only production dependencies, list sequelize-cli under dependencies instead, or the start command has no CLI to run. Installing it locally also avoids the question's global install.

  4. Make the API wait for a healthy database. Starting the migration before PostgreSQL accepts connections only moves the failure. Compose's depends_on with condition: service_healthy waits for the dependency's healthcheck (see the Compose services reference):

    yaml
    services:
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: app
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
          interval: 2s
          timeout: 3s
          retries: 20
      api:
        build:
          context: ./app
          dockerfile: fixed.dockerfile
        depends_on:
          db:
            condition: service_healthy

    The config/config.json used with it points at the Compose service name, not at localhost:

    json
    {
      "development": {
        "username": "postgres",
        "password": "postgres",
        "database": "app",
        "host": "db",
        "dialect": "postgres"
      }
    }

The question's Dockerfile also runs sequelize db:seed:all at build time. It reads the same config and needs the same database, so it belongs in the start command as well, after the migration.

Confirm the migration ran#

Start the stack with docker compose up -d --build and read the API logs with docker compose logs api. In the reproduction the first start printed:

text
Sequelize CLI [Node: 22.23.3, CLI: 6.6.5, ORM: 6.37.8]
 
Loaded configuration file "config/config.json".
Using environment "development".
== 20261012000000-create-notes: migrating =======
== 20261012000000-create-notes: migrated (0.034s)
 
app started

The CLI records applied migrations in the SequelizeMeta table, which you can query from the database container:

text
              name              
--------------------------------
 20261012000000-create-notes.js
(1 row)

Running migrations on every start is safe because of that table. After docker compose restart api, the second start skipped the work:

text
Loaded configuration file "config/config.json".
Using environment "development".
No migrations were executed, database schema was already up to date.
app started

When the config lives somewhere else, or is not shipped at all#

The same message shows up in setups where the Dockerfile order is already right. The first cases below were run inside the working container from the previous section; the .dockerignore case used a rebuilt image run on its own.

docker exec from another directory. Running the CLI by its full path from / (docker compose exec -w / api /app/node_modules/.bin/sequelize-cli db:migrate:status) brings the original error straight back, even though the file is in the image:

text
ERROR: Cannot find "/config/config.json". Have you run "sequelize init"?

Run it from the project directory, or pass the config path explicitly. From /, adding the config file and migrations folder paths as options worked:

text
Loaded configuration file "app/config/config.json".
Using environment "development".
up 20261012000000-create-notes.js

Config in a non-default folder. With the config moved to db/config/config.json, the CLI reports /app/config/config.json as missing. A .sequelizerc in the project root fixes it; the Sequelize migrations guide documents this file and its keys, and this is the one that was tested:

js
const path = require('path');
 
module.exports = {
  config: path.resolve('db', 'config', 'config.json'),
  'migrations-path': path.resolve('db', 'migrations'),
};
text
Loaded configuration file "db/config/config.json".
Using environment "development".
up 20261012000000-create-notes.js

It only helps when the CLI starts in that directory: the same command run from / failed again with /config/config.json, because the CLI never read the .sequelizerc in /app.

Config excluded from the build context. A config line in .dockerignore (common when the file holds credentials) removes the folder from the image, and the container fails at start with Cannot find "/app/config/config.json". Check what COPY . . actually received; the Vite index.html page shows the same .dockerignore trap with another file. If you keep credentials out of the image on purpose, the migrations guide documents a connection-string option instead of a config file; in the reproduction, a run with a connection URL and no config file at all printed Parsed url postgres://postgres:*****@db:5432/app and listed the migration.

The suggestion in the message. "Have you run sequelize init?" is a hint for a new project, not a fix here. When no config file exists, init writes a default config.json from getDefaultConfig(), which uses the MySQL dialect, user root and host 127.0.0.1. The CLI stops complaining about the file, and the next step fails against a database that is not there.

The next error after the config is found#

Once the CLI reads your config, the failure moves to the connection. If the migration still runs during the build, it is getaddrinfo ENOTFOUND db, as shown above. If the config says "host": "localhost" (as the sequelize init answer on the question does), the CLI inside the container dials its own loopback; continue with ECONNREFUSED 127.0.0.1:5432 in Docker Compose, which explains why localhost is the container itself and how to wait for a healthy database. If the app's own sequelize.authenticate() then waits forever instead of failing, see Sequelize authenticate() hangs with no success or error, which traces it to the pg connection timeout default of no timeout. And if your Dockerfile also compiles TypeScript before migrating, the same production-install question decides whether tsc is there; sh: tsc: not found in Docker build covers it. A Next.js image fails the same way when a statically rendered page fetches another Compose service during docker build; getaddrinfo ENOTFOUND only in the production Next.js image moves that fetch to request time.

The useful habit is to read the absolute path in the message literally. It is the CLI's working directory plus config/config.json, nothing more, so it tells you which directory the command ran in and therefore which Dockerfile line, exec call or start command to change. Reproduced on 10 October 2026 with Docker Engine 29.7.2, node:22-alpine (Node 22.23.3), postgres:16-alpine, sequelize-cli 6.6.5 and Sequelize 6.37.8.

Further reading: Fix: React hot reload doesn't work in a Docker container.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.