docs/community/self-hosting-novu/deploy-with-docker.mdx
Docker compose is the easiest way to get started with self-hosted Novu. This guide will walk you through the steps to run all services in single virtual machine using docker compose. This guide uses latest docker images. If you are looking to self host 0.24.x version, checkout 0.24.x docs
You need the following installed in your system:
curl and openssl (pre-installed on most systems)Run the setup script to download the required files, generate secure secrets, and start Novu:
curl -fsSL https://raw.githubusercontent.com/novuhq/novu/next/docker/community/setup.sh | bash
To install into a specific directory, set NOVU_DIR:
curl -fsSL https://raw.githubusercontent.com/novuhq/novu/next/docker/community/setup.sh | NOVU_DIR=~/novu bash
The script will:
docker-compose.yml and .env.example into the target directory (defaults to ./novu)JWT_SECRET, STORE_ENCRYPTION_KEY, and NOVU_SECRET_KEY.env file with the generated secretsdocker compose up -dOnce complete, visit http://localhost:4000 to start using Novu.
<Note> If you already have a `.env` file in the target directory, the script will only fill in any missing secret values without overwriting existing configuration. </Note>When deploying to a VPS, update your .env file with your server's information:
# Replace <vps-ip-address> with your VPS IP address
HOST_NAME=http://<vps-ip-address>
Start Novu on your VPS:
docker compose up -d
Access your dashboard at http://vps-ip-address:4000.
If you used the setup script, secure random secrets were generated automatically for JWT_SECRET, STORE_ENCRYPTION_KEY, and NOVU_SECRET_KEY. If you cloned the repository manually, update the .env file with your own secrets before going to production.
JWT_SECRET: Used by the API to generate JWT keys.STORE_ENCRYPTION_KEY: Used to encrypt/decrypt the provider credentials. It must be 32 characters long.HOST_NAME: Host name of your installation:
http://localhosthttp://<vps-ip-address>) or domain nameREDIS_CACHE_SERVICE_HOST and REDIS_HOST can have same value for small deployments. For larger deployments, it is recommended to use separate Redis instances for caching and queue management.To keep the setup simple, we made some choices that may not be optimal for production:
We strongly recommend that you decouple your database before deploying.
This section explains how to integrate the Novu Inbox component into your application when using a self-hosted Novu deployment.
npm install @novu/react react-router-dom
Create a component file (e.g., inbox.tsx) in your project:
import React from 'react';
import { Inbox } from '@novu/react';
import { useNavigate } from 'react-router';
export function NotificationCenter() {
const navigate = useNavigate();
return (
<Inbox
applicationIdentifier="YOUR_APPLICATION_IDENTIFIER"
subscriber="YOUR_SUBSCRIBER_ID"
backendUrl="http://<your-docker-host>:3000" // Docker host address where Novu API is running
socketUrl="http://<your-docker-host>:3002" // Docker host address where Novu socket is running
routerPush={(path: string) => navigate(path)}
/>
);
}
Adjust the backendUrl and socketUrl based on your deployment:
Once your application is running, you should see the bell icon in your navbar. Clicking it will open the notification inbox UI.
To test notifications, create and trigger a workflow from your self-hosted Novu dashboard, selecting In-App as the channel.
For more information on customizing the Inbox component, refer to the Inbox documentation.
When using a self-hosted Novu deployment with your backend services, configure the server SDK to connect to your Docker-hosted Novu API instance.
Configure the SDK with your self-hosted backend URL:
<Tabs> <Tab title="Node.js"> ```typescript import { Novu } from '@novu/api';const novu = new Novu({ secretKey: '<YOUR_SECRET_KEY_HERE>', serverURL: 'http://<your-docker-host>:3000', });
</Tab>
<Tab title="Python">
```python
import os
from novu_py import Novu
novu = Novu(
secret_key=os.getenv('NOVU_SECRET_KEY', ''),
server_url='http://<your-docker-host>:3000',
)
s := novugo.New( novugo.WithServerURL("http://<your-docker-host>:3000"), novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")), )
</Tab>
<Tab title="PHP">
```php
use novu;
$novu = novu\Novu::builder()
->setServerURL('http://<your-docker-host>:3000')
->setSecurity('<YOUR_SECRET_KEY_HERE>')
->build();
var novu = new NovuSDK( serverUrl: "http://<your-docker-host>:3000", secretKey: "<YOUR_SECRET_KEY_HERE>" );
</Tab>
<Tab title="Java">
```java
import co.novu.Novu;
Novu novu = Novu.builder()
.serverUrl("http://<your-docker-host>:3000")
.secretKey("<YOUR_SECRET_KEY_HERE>")
.build();
Adjust the backendUrl based on your deployment:
Once initialized, you can trigger notification events:
<Tabs> <Tab title="Node.js"> ```typescript await novu.trigger({ workflowId: 'workflowId', to: { subscriberId: 'subscriberId' }, payload: { name: 'John Doe', orderId: 'ORDER_ID_123', }, }); ``` </Tab> <Tab title="Python"> ```python novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto( workflow_id='workflowId', to={'subscriber_id': 'subscriberId'}, payload={'name': 'John Doe', 'orderId': 'ORDER_ID_123'}, )) ``` </Tab> <Tab title="Go"> ```go _, err := s.Trigger(ctx, components.TriggerEventRequestDto{ WorkflowID: "workflowId", To: components.CreateToSubscriberPayloadDto(components.SubscriberPayloadDto{ SubscriberID: "subscriberId", }), Payload: map[string]any{ "name": "John Doe", "orderId": "ORDER_ID_123", }, }, nil) ``` </Tab> <Tab title="PHP"> ```php $novu->trigger( triggerEventRequestDto: new Components\TriggerEventRequestDto( workflowId: 'workflowId', to: new Components\SubscriberPayloadDto(subscriberId: 'subscriberId'), payload: ['name' => 'John Doe', 'orderId' => 'ORDER_ID_123'], ), ); ``` </Tab> <Tab title=".NET"> ```csharp await novu.TriggerAsync(new TriggerEventRequestDto { WorkflowId = "workflowId", To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto { SubscriberId = "subscriberId" }), Payload = new Dictionary<string, object>() { { "name", "John Doe" }, { "orderId", "ORDER_ID_123" }, }, }); ``` </Tab> <Tab title="Java"> ```java novu.trigger().body(TriggerEventRequestDto.builder() .workflowId("workflowId") .to(To2.of(SubscriberPayloadDto.builder().subscriberId("subscriberId").build())) .payload(Map.of("name", "John Doe", "orderId", "ORDER_ID_123")) .build()).call(); ``` </Tab> <Tab title="cURL"> ```bash curl -X POST 'http://<your-docker-host>:3000/v1/events/trigger' \ -H 'Content-Type: application/json' \ -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \ -d '{ "name": "workflowId", "to": { "subscriberId": "subscriberId" }, "payload": { "name": "John Doe", "orderId": "ORDER_ID_123" } }' ``` </Tab> </Tabs>For more information on using the server SDKs, refer to the Server-Side SDKs documentation.
The bridge application is application where workflow definition are written using @novu/framework. Here's how to set it up:
# Initialize the bridge application
npx novu@latest init \
--secret-key=<secret_key> \
--api-url=http://localhost:3000
# Install dependencies
npm install
# Start the bridge application on a port that does not conflict with the Dashboard (4000)
npm run dev -- --port 4005
A Next.js bridge application with a sample @novu/framework workflow runs on the port you choose (for example http://localhost:4005). Visit http://localhost:4005/api/novu to verify the bridge endpoint.
Novu Local Studio is a development environment that allows you to test and manage your workflows. The setup varies based on your deployment:
<Tabs> <Tab title="Local">if novu is run using above docker compose command in local machine, use below commmand
npx novu@latest dev -d http://localhost:4000 -p 4005
Following actions will occur:
<bridge_application_port>http://localhost:4000 as dashboard urlUsing bridge application url as bridge url
To use bridge application url as bridge url, use below command:
npx novu@latest dev -d http://localhost:4000 -p 4005 -t http://host.docker.internal:4005
# update the bridge .env file with below variables
NOVU_API_URL=http://<vps-ip-address>:3000
# Start Novu Studio with your VPS dashboard URL and bridge application URL
npx novu@latest dev -d http://<vps-ip-address>:4000
Check all available flags with npx novu dev command
npx novu@latest sync \
--bridge-url <tunnel-url>/api/novu \
--api-url http://localhost:3000 \
--secret-key <secret_key>
npx novu@latest sync \
--bridge-url <tunnel_url>/api/novu \
--api-url http://<vps-ip-address>:3000 \
--secret-key <secret_key>
When deploying to a VPS, consider these additional security measures:
.env fileWhen self-hosting Novu, configure your server SDK with the self-hosted serverURL (or server_url) before triggering events.
const novu = new Novu({ secretKey: '<YOUR_SECRET_KEY_HERE>', serverURL: 'http://<your-docker-host>:3000', });
await novu.trigger({ workflowId: 'workflowId', to: { subscriberId: 'subscriberId' }, payload: {}, });
</Tab>
<Tab title="Python">
```python
from novu_py import Novu
novu = Novu(
secret_key='<YOUR_SECRET_KEY_HERE>',
server_url='http://<your-docker-host>:3000',
)
novu.trigger(trigger_event_request_dto={
'workflow_id': 'workflowId',
'to': {'subscriber_id': 'subscriberId'},
'payload': {},
})
_, err := s.Trigger(ctx, components.TriggerEventRequestDto{ WorkflowID: "workflowId", To: components.CreateToSubscriberPayloadDto(components.SubscriberPayloadDto{ SubscriberID: "subscriberId", }), Payload: map[string]any{}, }, nil)
</Tab>
<Tab title="PHP">
```php
$novu = novu\Novu::builder()
->setServerURL('http://<your-docker-host>:3000')
->setSecurity('<YOUR_SECRET_KEY_HERE>')
->build();
$novu->trigger(
triggerEventRequestDto: new Components\TriggerEventRequestDto(
workflowId: 'workflowId',
to: new Components\SubscriberPayloadDto(subscriberId: 'subscriberId'),
payload: [],
),
);
await novu.TriggerAsync(new TriggerEventRequestDto { WorkflowId = "workflowId", To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto { SubscriberId = "subscriberId" }), });
</Tab>
<Tab title="Java">
```java
Novu novu = Novu.builder()
.serverUrl("http://<your-docker-host>:3000")
.secretKey("<YOUR_SECRET_KEY_HERE>")
.build();
novu.trigger().body(TriggerEventRequestDto.builder()
.workflowId("workflowId")
.to(To2.of(SubscriberPayloadDto.builder().subscriberId("subscriberId").build()))
.build()).call();
We are introducing the first stage of caching in our system to improve performance and efficiency. Caching is turned off by default, but can easily be activated by setting the following environment variables:
Currently, caching is applied in the most heavily loaded areas of the system: Inbox feed and unseen-count requests, as well as common DAL requests during the trigger-event flow.
To implement a reverse-proxy or load balancer in front of Novu, you need to set the GLOBAL_CONTEXT_PATH for the base path of the application. This is the path that the application will be served from after the domain. For example: - company.com/novu This is used to set the base path for the application, and is used to set the base path for the API, Dashboard, and WebSocket connections.
The following environment variables set the context path for each public service: API_CONTEXT_PATH, WS_CONTEXT_PATH, WEBHOOK_CONTEXT_PATH, and FRONT_BASE_CONTEXT_PATH.
These can be set independently or together with GLOBAL_CONTEXT_PATH.
For example, to serve Novu from company.com/novu, set GLOBAL_CONTEXT_PATH=novu, then set API_CONTEXT_PATH=api and WS_CONTEXT_PATH=ws. That produces:
company.com/novu/apicompany.com/novu/wsYou can also set a service context path without GLOBAL_CONTEXT_PATH. For example, API_CONTEXT_PATH=novu-api exposes the API at company.com/novu-api.
The Dashboard container is served separately on its configured port (default 4000) and is not controlled by these API/WS context-path variables.
<Note> These env variables should be present on all services novu provides due to tight coupling. </Note>For example, you can use the following command:
```bash
npx novu@latest dev -d http://my-self-hosted-novu-domain.com:my-port
```