Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 265 additions & 113 deletions learn/developers/deploying-from-ci.mdx

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions learn/developers/multiple-applications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Harper isolates each application's module context automatically (see [What co-lo
Harper runs as a single process. Every co-located application shares that process, and, unless it is isolated, its worker threads, so it is worth being precise about what is isolated between applications and what is not.

- **Application source is isolated; some dependencies are not.** Harper loads each application's JavaScript in its own module context using Node.js's VM module loader, giving every application a distinct module cache. One application's own modules, imports, and module-scoped state are not visible to another, so two applications can depend on different packages—or different versions of the same package—without colliding. The exception is dependencies: under the default `dependencyLoader: auto`, packages that do not declare `harper` as a dependency load through Node's loader and share its cache within that worker, so two applications in the same worker resolving the same package file get the same instance and the same singleton state. Different workers have separate caches. Both the loader and the dependency policy are configurable—see [Module Loading](/reference/v5/components/module-loading).
- **Database data is shared by default.** The `tables` and `databases` APIs normally reach the same base databases, whether imported from `harper` or used as globals. An isolated worker has its own API objects but still reaches that shared data. The exception is a database an application [forks](#a-private-fork-of-a-database): its `harper` import of `databases` resolves that name to the fork, while the bare globals still reach the base. A record written to a base database is visible to applications using that same base; writes to a fork stay in that fork. Any application can still read or write unbranched tables in-process. This is what makes co-location efficient, and it is why separate databases are a [namespacing convention](#namespacing-data-by-database) rather than an enforced boundary.
- **Database data is shared by default.** The `tables` and `databases` APIs normally reach the same base databases, whether imported from `harper` or used as globals. An isolated worker has its own API objects but still reaches that shared data. The exception is a database an application [forks](#a-private-fork-of-a-database): its `harper` import of `databases` resolves that name to the fork. The bare globals still reach the base in the shared workers under the default module loader; from v5.4.0 they reach the fork in an isolated application, and under the `vm` and `compartment` loaders. A record written to a base database is visible to applications using that same base; writes to a fork stay in that fork. Any application can still read or write unbranched tables in-process. This is what makes co-location efficient, and it is why separate databases are a [namespacing convention](#namespacing-data-by-database) rather than an enforced boundary.
- **Users, roles, and sessions are instance-wide.** Harper's RBAC belongs to the instance, not to an application. Every application's `roles.yaml` reconciles into the same instance-wide role registry, and a user authenticates against the instance as a whole. See [Access control is instance-wide](#access-control-is-instance-wide).
- **Worker threads are shared, unless an application is isolated.** By default every worker thread loads every application, so applications share each thread's globals and are restarted together. As of v5.3.0, an application whose root-config entry sets `isolated: true` runs instead in a worker thread of its own that loads no other application, and a redeploy that keeps it isolated restarts only that thread. Its worker listens only on its own Unix domain socket, not on Harper's ports, so it needs a proxy in front that routes its own `host` to that socket. On Harper Fabric the platform does this for a host the cluster claims. See [Isolated applications](/reference/v5/components/applications#isolated-applications) for the requirements and the `threads.maxIsolated` cap.
- **The process is shared.** Because every application runs in one process, operational actions apply to all of them: restarting the instance restarts every co-located application, and applications cannot change the process working directory. Plan restarts and deployments with the whole instance in mind.
Expand Down Expand Up @@ -103,7 +103,7 @@ An application can also declare `host` on an individual plugin, but a `host` on
A mount is a routing prefix, not an isolation boundary. Two limits matter when several applications share an instance:

- **A mount does not namespace resources.** REST endpoint paths come from the resources and tables an application exports, and those exports land in the worker's shared registry for the applications it loads. Mounting namespaces the external URL an application answers on; it does not make two applications' exports independent. Two applications that both export a `User` resource still conflict, wherever each is mounted. Give each application uniquely named resources or, preferably, its own database. An [isolated](/reference/v5/components/applications#isolated-applications) application is the exception: its exports are registered only in its own worker thread.
- **A mount does not host-constrain Fastify routes.** [`fastifyRoutes`](/reference/v5/fastify-routes/overview) registers as a global fallback outside the routed middleware chain, so those routes answer on every hostname. A `urlPath` mount does apply—it becomes the Fastify route prefix—but a `host` mount does not, and Harper refuses to load a host-mounted application that declares `fastifyRoutes` rather than silently serving it unconstrained. Port those routes to [custom resources](/reference/v5/resources/overview) or `server.http()` before mounting the application by host.
- **A mount does not host-constrain Fastify routes.** [`fastifyRoutes`](/reference/v5/fastify-routes/overview) registers as a global fallback outside the routed middleware chain, so those routes answer on every hostname. A `urlPath` mount does apply—it becomes the Fastify route prefix—but a `host` mount does not, and Harper refuses to load a host-mounted application that declares `fastifyRoutes` rather than silently serving it unconstrained. Port those routes to [custom resources](/reference/v5/resources/overview) or `server.http()` before mounting the application by host. From v5.4.0, an [isolated](/reference/v5/components/applications#isolated-applications) application with a `host` loads its `fastifyRoutes` as they are, since its worker serves no other application.

### Custom dispatch in the middleware chain

Expand Down Expand Up @@ -152,7 +152,7 @@ listings-api-staging:

Comment thread
dawsontoth marked this conversation as resolved.
Resolved
Comment thread
dawsontoth marked this conversation as resolved.
Resolved
The first time the application loads, Harper takes a snapshot of each named database as the application's fork. The `databases` the application imports from `harper` then resolves those names to its fork, so its code is unchanged, and every other application keeps using the base. The `tables` import follows only a fork of the default `data` database, so reach any other branched database, like `listings` here, through `databases`. The fork is durable and is never refreshed from the base. It is not replicated: each node of a cluster keeps its own. `drop_component` with `restart=true` removes it. Without `restart`, the shared workers still have it loaded, so it stays until they restart; from v5.4.0 Harper then removes it on its own, and before v5.4.0 you drop the application again with `restart=true`. Dropping an isolated application removes its fork with or without `restart` (v5.4.0), and `keepBranches` keeps it. See [Removing a fork](/reference/v5/components/applications#removing-a-fork).

A fork is still not an enforced boundary. The bare `databases` and `tables` globals reach the base. The application can also still open any database that it did not branch. On v5.3.0 and v5.3.1, an application deployed by `package` or `by_ref` gets no fork at all ([harper#3071](https://github.com/HarperFast/harper/issues/3071)). See [Branched databases](/reference/v5/components/applications#branched-databases) for the requirements and the manual steps that work in the meantime.
A fork is still not an enforced boundary. In the shared workers, under the default module loader, the bare `databases` and `tables` globals reach the base; from v5.4.0 they reach the fork in an isolated application, and under the `vm` and `compartment` loaders. The application can also still open any database that it did not branch. On v5.3.0 and v5.3.1, an application deployed by `package` or `by_ref` gets no fork at all ([harper#3071](https://github.com/HarperFast/harper/issues/3071)). See [Branched databases](/reference/v5/components/applications#branched-databases) for the requirements and the manual steps that work in the meantime.

When a boundary has to hold against code you do not fully trust, or against a security or compliance requirement, put the application on a separate instance or cluster. That is the only boundary Harper enforces for data.

Expand Down
Loading
Loading