DEV Community

stale_orbit
stale_orbit

Posted on

NocoBase in Docker: a fine place to run, a poor place to build

The NocoBase plugin development docs assume one of two starting points: a project created with create-nocobase-app, or an installation from the Git source. Neither is what most people actually have in front of them. What you have is the container you spun up with docker compose up -d a week ago, database already populated, and building a plugin there would obviously be quicker.

Someone tried exactly that and posted about it on the official forum (t/12732, May 2026): compilation failed in the Docker environment because @nocobase/devtools wasn't installed. The thread ends with one reply asking for a full tutorial, and nothing after that. The report is also from the 2.0 line.

So I ran it end to end on 2.1.23. You can make it work — but you shouldn't, and the reason isn't the two missing dependencies. It's what happens afterwards.

Test setup: NocoBase 2.1.23 (official Docker image nocobase/nocobase:2.1.23) + PostgreSQL 16. Everything done through the CLI inside the container (yarn pm, yarn build), with the implementation in @nocobase/cli-v1 read where behavior needed explaining.

Obstacle 1: scaffolding reports success and produces nothing

yarn pm create @scope/plugin-hello
Enter fullscreen mode Exit fullscreen mode

It ends with Done in 5.57s. and exit code 0. Then:

$ ls /app/nocobase/packages/plugins/
.gitkeep     # nothing new
Enter fullscreen mode Exit fullscreen mode

Scrolling back through the output, there's an error buried in it:

Cannot find module '@umijs/utils'
Require stack:
- /app/nocobase/node_modules/@nocobase/cli-v1/src/plugin-generator.js
Enter fullscreen mode Exit fullscreen mode

The generator module can't resolve a dependency and dies, while the command as a whole still exits successfully. If you don't read the log you're left hunting for a directory that was never created. I hunted for a while.

The fix is what it looks like:

yarn add @umijs/utils -W
Enter fullscreen mode Exit fullscreen mode

After that the scaffold appears, including the V2 page entry points:

packages/plugins/@scope/plugin-hello/
├── package.json
├── src/index.ts
├── client.js / client.d.ts
├── client-v2.js / client-v2.d.ts
└── server.js / server.d.ts
Enter fullscreen mode Exit fullscreen mode

Obstacle 2: the build asks for "all dependencies"

yarn build @scope/plugin-hello
Enter fullscreen mode Exit fullscreen mode
Please install all dependencies
$ yarn install
error Command failed with exit code 1.
Enter fullscreen mode Exit fullscreen mode

This one fails honestly — exit code 1 — but the message doesn't say which dependency. The check turns out to be a single condition:

// @nocobase/cli-v1, util.js
exports.hasTsNode = () => exports.isPackageValid('ts-node/dist/bin');

exports.nodeCheck = () => {
  if (!exports.hasTsNode()) {
    console.log('Please install all dependencies');
    process.exit(1);
  }
};
Enter fullscreen mode Exit fullscreen mode

It only looks for ts-node. What brings ts-node along is @nocobase/devtools — the package named in the forum report.

yarn add @nocobase/devtools@2.1.23 -W -D    # match your app version
Enter fullscreen mode Exit fullscreen mode

Now the build runs:

Rspack 1.7.8 compiled successfully in 38 ms
@scope/plugin-hello: build plugin server source
@scope/plugin-hello: build declaration
Done in 8.99s.
Enter fullscreen mode Exit fullscreen mode

And yarn pm enable @scope/plugin-hello reports success. At this point the forum report is confirmed: add two dependencies and you can build plugins in the Docker image.

The interesting part starts here.

Obstacle 3: recreate the container and it's all gone

Sooner or later you bump an image tag or change a setting, which means docker compose down && docker compose up -d. Afterwards:

the plugin           : No such file or directory
@nocobase/devtools   : No such file or directory
@umijs/utils         : No such file or directory
Enter fullscreen mode Exit fullscreen mode

Obvious in hindsight — all of it lives in the container's own filesystem layer. The only thing that survives is whatever a volume is mounted onto, which in a standard setup is exactly one path:

volume nocobase-storage -> /app/nocobase/storage      # the only survivor
Enter fullscreen mode Exit fullscreen mode

Plugins are scaffolded into packages/plugins/, which is outside that. (storage/plugins/ did still hold its contents after the recreate, which matters for the recommendation below.)

And then it runs while broken

The files are gone. The database still says the plugin is installed and enabled:

SELECT name, enabled, installed FROM "applicationPlugins" WHERE name = '@scope/plugin-hello';
 @scope/plugin-hello | t | t
Enter fullscreen mode Exit fullscreen mode

So every startup logs this:

Cannot find plugin '@scope/plugin-hello'
  at PluginManager.getPackageJson (...)
Enter fullscreen mode Exit fullscreen mode

And the application returns HTTP 200 and serves traffic normally. It doesn't crash, which is precisely the problem: without reading the logs you have no signal at all. I ran into the same "boots fine, internally broken" shape once before, when testing a version downgrade — NocoBase does not stop for this class of inconsistency, so you need your own way of noticing.

Cleanup is one statement:

DELETE FROM "applicationPlugins" WHERE name = '@scope/plugin-hello';
Enter fullscreen mode Exit fullscreen mode

What to do instead

Build somewhere else. The reason the docs assume create-nocobase-app or a Git checkout is that those give you an environment where the tooling is present and your work doesn't evaporate. The Docker image is built for running: its devDependencies are empty, which is a deliberate choice rather than an oversight. Fighting that costs more than it saves.

If you insist on Docker, give the output somewhere to live. Mount packages/ from a volume or a bind mount and at least the plugin survives. The dependencies won't — they land in node_modules — so you're reinstalling them after every container recreate. Automating that is more work than standing up a proper development environment.

Let the Docker instance be the thing that runs. Build elsewhere and bring the result in through storage/plugins/, which is volume-backed. Which is to say: separate building from running, the way you would with anything else.

One more thing, if you're behind a corporate proxy

On a network with TLS inspection, yarn add inside the container fails with self-signed certificate in certificate chain. Copy your CA bundle in with docker cp and point both NODE_EXTRA_CA_CERTS and yarn's cafile at the copy.

Copy it — don't bind-mount the host's system CA file into the container. That's a good way to damage the host's certificate store, which I mention because I have done it.

Takeaways

  • Stepping outside the documented prerequisites costs two missing dependencies: @umijs/utils for scaffolding, @nocobase/devtools (for ts-node) for building.
  • pm create exits 0 even when it fails. Read the log; don't trust the status code.
  • Those fixes work — until you recreate the container, which erases the plugin and the dependencies alike. Only volume-mounted paths survive.
  • Afterwards the database still lists the plugin as enabled, so the app logs an error on every boot and returns HTTP 200 regardless. Nothing crashes, so nothing tells you.
  • Use the Docker image to run, and build in the environment the docs assume.

The instinct to use the environment already in front of you is a reasonable one. In this case the cost of stepping outside the assumptions came back in full. My guess is the forum report stopped at "add the dependency and it compiles" simply because nobody carried it as far as recreating the container.

(Measured on 2.1.23 / PostgreSQL 16. Behavior may change in future versions.)

References

Top comments (0)