> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novu.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy with Docker

> Deploy Novu on your own infrastructure using Docker Compose, configure environment variables, and run the API, worker, and web dashboard locally.

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](https://v0.x-docs.novu.co/self-hosting-novu/deploy-with-docker)

## Prerequisites

You need the following installed in your system:

* [Docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/)
* `curl` and `openssl` (pre-installed on most systems)

## Quick Start

Run the setup script to download the required files, generate secure secrets, and start Novu:

```bash theme={null}
curl -fsSL https://raw.githubusercontent.com/novuhq/novu/next/docker/community/setup.sh | bash
```

To install into a specific directory, set `NOVU_DIR`:

```bash theme={null}
curl -fsSL https://raw.githubusercontent.com/novuhq/novu/next/docker/community/setup.sh | NOVU_DIR=~/novu bash
```

The script will:

1. Download `docker-compose.yml` and `.env.example` into the target directory (defaults to `./novu`)
2. Generate cryptographically random values for `JWT_SECRET`, `STORE_ENCRYPTION_KEY`, and `NOVU_SECRET_KEY`
3. Create a `.env` file with the generated secrets
4. Start all Novu services via `docker compose up -d`

Once complete, visit [http://localhost:4000](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>

### Configure Environment

#### VPS Deployment

When deploying to a VPS, update your `.env` file with your server's information:

```bash theme={null}
# Replace <vps-ip-address> with your VPS IP address
HOST_NAME=http://<vps-ip-address>
```

Start Novu on your VPS:

```bash theme={null}
docker compose up -d
```

Access your dashboard at [http://vps-ip-address:4000](http://vps-ip-address:4000/).

## Securing Your Setup

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.

### Required Variables:

* `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:
  * To run in local machine: `http://localhost`
  * To run in VPS: Your server's IP address (e.g., `http://<vps-ip-address>`) or domain name
* `REDIS_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.

## Configuration

To keep the setup simple, we made some choices that may not be optimal for production:

* the database is in the same machine as the servers
* the storage uses localstack instead of S3

We strongly recommend that you decouple your database before deploying.

## Setting Up the Inbox Component

This section explains how to integrate the Novu Inbox component into your application when using a self-hosted Novu deployment.

### Install the required packages

```bash theme={null}
npm install @novu/react react-router-dom
```

### Create the Inbox component

Create a component file (e.g., `inbox.tsx`) in your project:

```tsx theme={null}
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)}
    />
  );
}
```

### Configure the environment URLs

Adjust the `backendUrl` and `socketUrl` based on your deployment:

### Testing the connection

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](/platform/inbox).

## Initializing the Server SDK

When using a self-hosted Novu deployment with your backend services, configure the server SDK to connect to your Docker-hosted Novu API instance.

### Install the package

<Tabs>
  <Tab title="Node.js">
    ```bash theme={null}
    npm install @novu/api
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install novu-py
    ```
  </Tab>

  <Tab title="Go">
    ```bash theme={null}
    go get github.com/novuhq/novu-go
    ```
  </Tab>

  <Tab title="PHP">
    ```bash theme={null}
    composer require novuhq/novu
    ```
  </Tab>

  <Tab title=".NET">
    ```bash theme={null}
    dotnet add package Novu
    ```
  </Tab>

  <Tab title="Java">
    Maven:

    ```xml theme={null}
    <dependency>
      <groupId>co.novu</groupId>
      <artifactId>novu-java</artifactId>
      <version>LATEST</version>
    </dependency>
    ```
  </Tab>
</Tabs>

### Initialize the SDK

Configure the SDK with your self-hosted backend URL:

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    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 theme={null}
    import os
    from novu_py import Novu

    novu = Novu(
        secret_key=os.getenv('NOVU_SECRET_KEY', ''),
        server_url='http://<your-docker-host>:3000',
    )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        novugo "github.com/novuhq/novu-go"
        "os"
    )

    s := novugo.New(
        novugo.WithServerURL("http://<your-docker-host>:3000"),
        novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")),
    )
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;

    $novu = novu\Novu::builder()
        ->setServerURL('http://<your-docker-host>:3000')
        ->setSecurity('<YOUR_SECRET_KEY_HERE>')
        ->build();
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;

    var novu = new NovuSDK(
        serverUrl: "http://<your-docker-host>:3000",
        secretKey: "<YOUR_SECRET_KEY_HERE>"
    );
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;

    Novu novu = Novu.builder()
        .serverUrl("http://<your-docker-host>:3000")
        .secretKey("<YOUR_SECRET_KEY_HERE>")
        .build();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # Set your self-hosted API base URL for REST calls
    export NOVU_API_URL=http://<your-docker-host>:3000
    export NOVU_SECRET_KEY=<YOUR_SECRET_KEY_HERE>
    ```
  </Tab>
</Tabs>

### Configure for different environments

Adjust the `backendUrl` based on your deployment:

### Triggering events

Once initialized, you can trigger notification events:

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    await novu.trigger({
      workflowId: 'workflowId',
      to: { subscriberId: 'subscriberId' },
      payload: {
        name: 'John Doe',
        orderId: 'ORDER_ID_123',
      },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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 theme={null}
    _, 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 theme={null}
    $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 theme={null}
    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 theme={null}
    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 theme={null}
    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](/platform/sdks).

### Setting up local development and bridge application

#### Setting Up the bridge application

The bridge application is application where workflow definition are written using `@novu/framework`. Here's how to set it up:

```bash theme={null}
# 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.

<Warning>
  The self-hosted Dashboard also runs on port **4000**. Use a different port (such as **4005**) for your bridge application to avoid a conflict.
</Warning>

#### Setting up local development

[Local development](/framework/studio) with `npx novu dev` opens the Novu Dashboard in Local mode, so you can test and manage the workflows running on your machine. 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

    ```bash theme={null}
    npx novu@latest dev -d http://localhost:4000 -p 4005
    ```

    Following actions will occur:

    * Your self-hosted Dashboard will open in the **Local** environment,
    * Novu will generate a tunnel url that will forward the request to bridge application running on `<bridge_application_port>`
    * The Local environment will use `http://localhost:4000` as dashboard url

    **Using bridge application url as bridge url**

    To use bridge application url as bridge url, use below command:

    ```bash theme={null}
    npx novu@latest dev -d http://localhost:4000 -p 4005 -t http://host.docker.internal:4005
    ```

    <Warning>
      In Windows OS, there are some additional steps:

      * stop the running docker compose process using `ctrl + c`
      * update the `docker-compose.yml` file and add below config with each service (api, dashboard, worker and ws)

      ```bash theme={null}
      extra_hosts:
          - "host.docker.internal:host-gateway"
      ```

      * start the docker compose process again using `docker compose up`
      * now you can use `host.docker.internal` as bridge url hostname inplace of `localhost`
    </Warning>
  </Tab>

  <Tab title="VPS">
    ```bash theme={null}
    # update the bridge .env file with below variables
    NOVU_API_URL=http://<vps-ip-address>:3000

    # Start local development with your VPS dashboard URL and bridge application URL
    npx novu@latest dev -d http://<vps-ip-address>:4000
    ```

    Check all [available flags](/framework/studio#novu-cli-flags) with `npx novu dev` command
  </Tab>
</Tabs>

### Synchronizing Workflows

<Tabs>
  <Tab title="Local">
    ```bash theme={null}
    npx novu@latest sync \
      --bridge-url <tunnel-url>/api/novu \
      --api-url http://localhost:3000 \
      --secret-key <secret_key>
    ```
  </Tab>

  <Tab title="VPS">
    ```bash theme={null}
    npx novu@latest sync \
      --bridge-url <tunnel_url>/api/novu \
      --api-url http://<vps-ip-address>:3000 \
      --secret-key <secret_key>
    ```
  </Tab>
</Tabs>

### VPS Security Considerations

When deploying to a VPS, consider these additional security measures:

1. Use a firewall to restrict access to only necessary ports
2. Set up SSL/TLS certificates for HTTPS access
3. Regularly update your Docker images and host system
4. Use strong, unique secrets in your `.env` file
5. Consider using a reverse proxy like Nginx for additional security layers

### Triggering events with custom installation

When self-hosting Novu, configure your server SDK with the self-hosted `serverURL` (or `server_url`) before triggering events.

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Novu } from '@novu/api';

    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 theme={null}
    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': {},
    })
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    s := novugo.New(
        novugo.WithServerURL("http://<your-docker-host>:3000"),
        novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")),
    )

    _, 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 theme={null}
    $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: [],
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    var novu = new NovuSDK(
        serverUrl: "http://<your-docker-host>:3000",
        secretKey: "<YOUR_SECRET_KEY_HERE>"
    );

    await novu.TriggerAsync(new TriggerEventRequestDto {
        WorkflowId = "workflowId",
        To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto { SubscriberId = "subscriberId" }),
    });
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    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();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    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": {}
    }'
    ```
  </Tab>
</Tabs>

### Caching

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:

* REDIS\_CACHE\_SERVICE\_HOST
* REDIS\_CACHE\_SERVICE\_PORT

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.

### Reverse-Proxy / Load Balancers

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:

* API: `company.com/novu/api`
* WS: `company.com/novu/ws`

You 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>

## FAQs

<AccordionGroup>
  <Accordion title="Local Tunnel and Self-Hosted Deployments">
    Novu uses a local tunnel as bridge url. It can be used as bridge url during local development (`npx novu dev`) and for testing purpose in development environment. It should not be used in production environment. It is recommended to use deployed application url as bridge url
  </Accordion>

  <Accordion title="When is Local Tunnel Not Required?">
    If the customer's application and the self-hosted Novu deployment are within the same network, there is no need for a local tunnel. In this case, the application can communicate directly with Novu through the internal network. Checkout `Using bridge application url as bridge url` section to learn more.
  </Accordion>

  <Accordion title="When is Local Tunnel Required?">
    If the application and Novu deployment reside on different networks, you can still interact with your self-hosted Novu instance using the Novu CLI. The CLI allows you to specify the Dashboard URL and Bridge Endpoint Origin to enable communication across networks via the Novu Cloud Local Tunnel.

    For example, you can use the following command:

    ```bash theme={null}
    npx novu@latest dev -d http://my-self-hosted-novu-domain.com:my-port
    ```
  </Accordion>
</AccordionGroup>
