# Install

> Install self-hosted Nodaro step by step: clone the repository, configure .env, secrets, migrations and object storage, start the stack, sign in and harden it.

Source: https://nodaro.ai/docs/self-hosting/install

This guide **installs** the Community Edition step by step, with the reasons behind each step. It is the same flow as the [Quickstart](https://nodaro.ai/docs/self-hosting/quickstart), plus managed services, secrets, storage and hardening. On the bundled compose stack, most steps need no action: read them to know what happens and what to change later.

## 1. Clone and configure

```bash
git clone https://github.com/nodaroai/app.nodaro.ai.git nodaro
cd nodaro
```

On the community compose stack, `.env` is optional. The compose file bundles Supabase, MinIO and Redis with working defaults. Create a `.env` only to add provider keys, to change the public URL, or to point Nodaro at your own managed services.

To start one, copy the example made for the compose stack:

```bash
cp .env.community.example .env
```

**Do not copy .env.example for the compose stack:** 
`.env.example` is the reference for installs outside the compose stack. Its placeholder values, such as `SUPABASE_URL=https://YOUR-PROJECT.supabase.co`, replace the compose defaults and break the bundled database.

The values you are most likely to set:

```bash
PUBLIC_URL=http://localhost:3000        # your install's public address

# Only with a managed Supabase project instead of the bundled one:
SUPABASE_URL=https://YOUR-PROJECT.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...
SUPABASE_ANON_KEY=eyJ...

# At least one model provider:
KIE_API_KEY=
REPLICATE_API_TOKEN=
ANTHROPIC_API_KEY=
ELEVENLABS_API_KEY=
```

The compose file passes a fixed list of variables to the app. A variable that is not on that list must be added to the `nodaro` service in `docker-compose.community.yml`. [Configuration](https://nodaro.ai/docs/self-hosting/configuration) lists every variable and says which ones `.env` can set.

## 2. Generate the internal secrets

Nodaro uses two secrets of its own:

| Variable | What it does |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | Authenticates the orchestrator to the API inside a Nodaro container. At least 32 characters. |
| `NODARO_ENCRYPTION_KEY` | A 64-character hex key that encrypts stored credentials: provider keys pasted on `/setup`, social network sign-in tokens, and the HTTP credentials users save for [Webhook Output](https://nodaro.ai/docs/nodes/publish/webhook-output). `SOCIAL_ENCRYPTION_KEY` is the older name and still works. |

**On the bundled compose stack, skip this step.** The container generates both at boot. It saves the encryption key in the `app-data` volume, at `/data/nodaro/encryption-key`, and reuses it on every later boot. Back up that volume together with the database.

**On your own orchestration**, such as a hosting platform or separate containers, set both yourself:

```bash
echo "INTERNAL_ORCHESTRATOR_SECRET=$(openssl rand -hex 32)" >> .env
echo "NODARO_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
```

Keep `NODARO_ENCRYPTION_KEY` safe and never change it: everything it encrypted becomes unreadable with another key.

## 3. Apply the database migrations

**On the bundled stack, this is automatic.** On boot, the app applies the files in `supabase/migrations/` before the API starts. It remembers which files it applied and skips them on the next boot. It refuses to start against a half-migrated database, and its log names the file that failed.

**With a managed Supabase project**, set `RUN_MIGRATIONS_ON_BOOT=false` and apply the migrations yourself. See [Database](https://nodaro.ai/docs/self-hosting/database#apply-the-migrations-to-a-managed-project).

## 4. Configure object storage

### The bundled MinIO

Nothing to configure. The compose file ships MinIO with these defaults:

| Variable | Default |
| --- | --- |
| `R2_ENDPOINT` | `http://minio:9000` |
| `R2_FORCE_PATH_STYLE` | `true` |
| `R2_BUCKET_NAME` | `nodaro-assets` |
| `R2_PUBLIC_URL` | `http://localhost:3000/storage/nodaro-assets` |

The app's web server serves the media under `/storage/`, so the browser and the backend read the same URL. The bucket is created on the first boot, with public read access. The media lives in the `minio-data` volume.

When you serve the install on a real domain, set `R2_PUBLIC_URL=https://<your-domain>/storage/nodaro-assets`. Change the MinIO credentials, `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY`, before you expose the stack.

### Cloudflare R2

Cloudflare R2 is recommended for real deployments, because it has no egress fees.

1. Create a bucket, for example `nodaro-assets`, and set `R2_BUCKET_NAME` to its name.
2. In the bucket's **Settings**, enable the public `r2.dev` address or attach a custom domain. Copy that URL into `R2_PUBLIC_URL`.
3. Under **Manage R2 API tokens**, create a token with **Object Read & Write** on this bucket. Copy its values into `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY` and `R2_ACCOUNT_ID`.
4. On the compose stack, also set the endpoint and the addressing style:

```bash
R2_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
R2_FORCE_PATH_STYLE=false
```

An empty value does not clear these two: the compose file falls back to the MinIO defaults for an empty variable. Outside the compose stack, leave both unset, and the endpoint is derived from `R2_ACCOUNT_ID`.

On boot, the log shows one harmless `[storage] failed to create bucket` line. R2 tokens cannot create buckets, and yours already exists.

### Other S3-compatible stores

For AWS S3, Backblaze B2, DigitalOcean Spaces, Supabase Storage or your own MinIO:

- Set `R2_ENDPOINT` to the store's S3 API URL.
- Set `R2_FORCE_PATH_STYLE=true` for most self-hosted servers.
- Set `R2_PUBLIC_URL` to the bucket's public URL.
- Set `R2_REGION` to the store's region, unless the store is Cloudflare R2 or MinIO.

`R2_REGION` defaults to `auto`, which is Cloudflare R2's own value and which MinIO ignores. AWS, DigitalOcean Spaces (`nyc3`, `fra1` and more) and a local Supabase (`local`) reject `auto`. Every request then fails with an authorization or endpoint error that does not mention the region.

### Make the media publicly readable

There are two ways, and most installs need only the first:

1. **A bucket policy.** This is the default. With a custom `R2_ENDPOINT`, the app creates the bucket at boot and gives it an anonymous-read policy. Cloudflare R2 does not need it: its public bucket setting covers it.
2. **A per-object ACL, with `STORAGE_OBJECT_ACL`.** For stores that refuse a bucket policy. DigitalOcean Spaces is the usual case: it refuses a bucket policy from a key scoped to one bucket. Set `STORAGE_OBJECT_ACL=public-read`, and every object the app writes carries that ACL.

Leave `STORAGE_OBJECT_ACL` empty unless you need the second way. When it is empty, no ACL header is sent. When it is set on a store whose keys lack the right to set ACLs, every upload fails. Nodaro accepts the standard canned ACLs — `private`, `public-read`, `public-read-write`, `authenticated-read`, `aws-exec-read`, `bucket-owner-read` and `bucket-owner-full-control` — and refuses any other value at boot.

## 5. Start the stack

```bash
docker compose -f docker-compose.community.yml up
```

The app image, `ghcr.io/nodaroai/nodaro-community`, is pulled prebuilt. The first boot downloads about 2.4 GB instead of compiling for 5 to 10 minutes, and later boots take seconds. The logs of Redis and the `nodaro` service appear side by side. When you see this line, the API is live:

```text
nodaro-1  | server listening on http://0.0.0.0:9000
```

The web server in the same container serves it on port `3000`. Open `http://localhost:3000`.

`latest` follows the `main` branch. To pin a release for a reproducible install, set `NODARO_IMAGE` in `.env`, for example `NODARO_IMAGE=ghcr.io/nodaroai/nodaro-community:v1.23.0`. [Updating](https://nodaro.ai/docs/self-hosting/updating) lists every tag.

To build the image from source instead, run `docker compose -f docker-compose.community.yml build`. You need this only when you change the code. The public URL, the port, the domain and the Supabase keys are read when the container starts, so the published image serves them after a restart.

## 6. Sign in for the first time

Sign up in the app with an email address and a password. The sign-in service creates the user, and Nodaro creates the user's profile automatically. On the bundled stack, no confirmation email is sent.

Community Edition users are unrestricted: there is no credit ledger and no admin panel. On the Business edition, promote your first admin next: see [First user and admin](https://nodaro.ai/docs/self-hosting/first-admin).

## Harden the install before you expose it

The compose defaults are made for local use, and they are public by definition. Before other people can reach the install:

### Mint fresh auth keys

```bash
node tools/generate-selfhost-keys.mjs >> .env
```

The script prints `SUPABASE_JWT_SECRET`, `SUPABASE_ANON_KEY` and `SUPABASE_SERVICE_ROLE_KEY`. The two keys are signed with the secret, so all three must come from the same run. A changed anon key needs no rebuild: the container hands it to the browser at runtime, in `/config.js`.

### Set new passwords

Set `POSTGRES_PASSWORD` and a matching `DATABASE_URL`:

```bash
POSTGRES_PASSWORD=<new-password>
DATABASE_URL=postgres://postgres:<new-password>@db:5432/postgres
```

Also set fresh MinIO credentials in `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY`.

The database aligns its internal role passwords with `POSTGRES_PASSWORD` only on its first start. If the `db-data` volume already exists, either delete the volume, which deletes your data, or change the role passwords by hand as `supabase_admin`.

### Serve it over HTTPS

Set `PUBLIC_URL` to your real `https://` address and put a reverse proxy in front of the stack. See [Reverse proxy and HTTPS](https://nodaro.ai/docs/self-hosting/reverse-proxy).

### Decide who can sign up

The Community Edition is made for a single operator. Anyone who can reach the sign-up page can create an account, and every signed-in user can change the provider keys. On the Business edition, only admins manage provider keys. See [Editions and surface profiles](https://nodaro.ai/docs/self-hosting/editions-and-profiles).

Apply every change with `docker compose -f docker-compose.community.yml up -d`, then check `/setup`.

## Frequently asked questions

### Which .env file should I start from for the compose stack?

Copy .env.community.example to .env. It lists only what the compose stack uses. The larger .env.example is a reference for installs outside the compose stack, and its placeholder Supabase values would replace the working defaults.

### Do I have to generate INTERNAL_ORCHESTRATOR_SECRET and NODARO_ENCRYPTION_KEY?

Not on the bundled compose stack, which generates both at boot and keeps the encryption key in the app-data volume. On your own orchestration, set both to 32 random bytes in hex, for example with openssl rand -hex 32.

### How do I use Cloudflare R2 instead of the bundled MinIO?

Set R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME and R2_PUBLIC_URL. On the compose stack, also set R2_ENDPOINT to your account's r2.cloudflarestorage.com endpoint and R2_FORCE_PATH_STYLE=false, because empty values fall back to the MinIO defaults.

### How do I know the install started correctly?

Wait for the log line server listening on http://0.0.0.0:9000, then open http://localhost:3000/setup. Every card should be green except the provider keys until you add one.

### What must I change before exposing the install to a network?

Mint fresh auth keys with node tools/generate-selfhost-keys.mjs, set a new POSTGRES_PASSWORD with a matching DATABASE_URL and new MinIO credentials, set PUBLIC_URL to your https address, and put a reverse proxy in front.
