# 설치

> 셀프 호스팅 Nodaro를 단계별로 설치하세요. 저장소 복제, .env, 시크릿, 마이그레이션, 오브젝트 스토리지 구성부터 스택 시작, 로그인, 보안 강화까지 다룹니다.

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

이 가이드에서는 Community Edition을 단계별로, 각 단계의 이유와 함께 **설치**합니다. 흐름은 [빠른 시작](https://nodaro.ai/docs/self-hosting/quickstart)과 같고, 관리형 서비스, 시크릿, 스토리지, 보안 강화에 대한 설명이 더해집니다. 번들된 Compose 스택에서는 대부분의 단계에 할 일이 없습니다. 무슨 일이 일어나는지, 나중에 무엇을 바꾸면 되는지 알기 위해 읽어 보세요.

## 1. 복제와 구성
```bash
git clone https://github.com/nodaroai/app.nodaro.ai.git nodaro
cd nodaro
```

Community Edition의 Compose 스택에서는 `.env`가 선택 사항입니다. Compose 파일에는 Supabase, MinIO, Redis가 작동하는 기본값과 함께 번들되어 있습니다. `.env`는 공급자 키를 추가하거나, 공개 URL을 바꾸거나, Nodaro가 내 관리형 서비스를 쓰게 할 때만 만드세요.

`.env`를 만들려면 Compose 스택용 예시 파일을 복사하세요.

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

**Compose 스택에는 .env.example을 복사하지 마세요:** 
`.env.example`은 Compose 스택 밖의 설치를 위한 참고 파일입니다. `SUPABASE_URL=https://YOUR-PROJECT.supabase.co` 같은 자리 표시자 값이 Compose 기본값을 덮어써서 번들된 데이터베이스가 작동하지 않게 됩니다.

가장 많이 설정하게 될 값은 다음과 같습니다.

```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=
```

Compose 파일은 정해진 변수 목록만 앱에 전달합니다. 이 목록에 없는 변수는 `docker-compose.community.yml`의 `nodaro` 서비스에 추가해야 합니다. [구성](https://nodaro.ai/docs/self-hosting/configuration)에서 모든 변수와 `.env`로 설정할 수 있는 변수를 확인하세요.

## 2. 내부 시크릿 생성
Nodaro는 자체 시크릿 두 개를 사용합니다.

| 변수 | 역할 |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | Nodaro 컨테이너 안에서 오케스트레이터가 API에 인증할 때 씁니다. 32자 이상이어야 합니다. |
| `NODARO_ENCRYPTION_KEY` | 저장된 자격 증명을 암호화하는 64자 16진수 키입니다. `/setup`에 붙여 넣은 공급자 키, 소셜 네트워크 로그인 토큰, 사용자가 [Webhook 출력](https://nodaro.ai/docs/nodes/publish/webhook-output)에 저장한 HTTP 자격 증명이 대상입니다. 예전 이름인 `SOCIAL_ENCRYPTION_KEY`도 계속 작동합니다. |

**번들된 Compose 스택에서는 이 단계를 건너뛰세요.** 컨테이너가 부팅할 때 둘 다 생성합니다. 암호화 키는 `app-data` 볼륨의 `/data/nodaro/encryption-key`에 저장하고, 이후 부팅할 때마다 다시 사용합니다. 이 볼륨은 데이터베이스와 함께 백업하세요.

**직접 구성한 오케스트레이션**, 예를 들어 호스팅 플랫폼이나 분리된 컨테이너에서는 둘 다 직접 설정하세요.

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

`NODARO_ENCRYPTION_KEY`는 안전하게 보관하고 절대 바꾸지 마세요. 다른 키로는 이 키로 암호화한 모든 것을 읽을 수 없게 됩니다.

## 3. 데이터베이스 마이그레이션 적용
**번들된 스택에서는 자동으로 진행됩니다.** 앱은 부팅할 때 API가 시작되기 전에 `supabase/migrations/`의 파일을 적용합니다. 적용한 파일을 기억해 두었다가 다음 부팅에서는 건너뜁니다. 마이그레이션이 중간에 멈춘 데이터베이스로는 시작하지 않으며, 로그에 실패한 파일 이름을 남깁니다.

**관리형 Supabase 프로젝트를 쓴다면** `RUN_MIGRATIONS_ON_BOOT=false`로 설정하고 마이그레이션을 직접 적용하세요. [데이터베이스](https://nodaro.ai/docs/self-hosting/database#apply-the-migrations-to-a-managed-project)를 참조하세요.

## 4. 오브젝트 스토리지 구성
### 번들된 MinIO
구성할 것이 없습니다. Compose 파일은 다음 기본값으로 MinIO를 제공합니다.

| 변수 | 기본값 |
| --- | --- |
| `R2_ENDPOINT` | `http://minio:9000` |
| `R2_FORCE_PATH_STYLE` | `true` |
| `R2_BUCKET_NAME` | `nodaro-assets` |
| `R2_PUBLIC_URL` | `http://localhost:3000/storage/nodaro-assets` |

앱의 웹 서버가 `/storage/` 아래에서 미디어를 제공하므로, 브라우저와 백엔드가 같은 URL을 읽습니다. 버킷은 첫 부팅 때 공개 읽기 권한으로 만들어집니다. 미디어는 `minio-data` 볼륨에 저장됩니다.

실제 도메인에서 설치를 제공할 때는 `R2_PUBLIC_URL=https://<your-domain>/storage/nodaro-assets`로 설정하세요. 스택을 공개하기 전에 MinIO 자격 증명인 `R2_ACCESS_KEY_ID`와 `R2_SECRET_ACCESS_KEY`를 바꾸세요.

### Cloudflare R2
실제 배포에는 Cloudflare R2를 권장합니다. 이그레스 요금이 없기 때문입니다.

1. 버킷을 만들고(예: `nodaro-assets`) `R2_BUCKET_NAME`을 그 이름으로 설정하세요.
2. 버킷의 **Settings**에서 공개 `r2.dev` 주소를 활성화하거나 커스텀 도메인을 연결하세요. 그 URL을 `R2_PUBLIC_URL`에 복사하세요.
3. **Manage R2 API tokens**에서 이 버킷에 대해 **Object Read & Write** 권한을 가진 토큰을 만드세요. 그 값을 `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_ACCOUNT_ID`에 복사하세요.
4. Compose 스택에서는 엔드포인트와 주소 지정 방식도 설정하세요.

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

빈 값으로는 이 두 변수가 지워지지 않습니다. Compose 파일은 빈 변수를 MinIO 기본값으로 대체합니다. Compose 스택 밖에서는 둘 다 설정하지 않은 채로 두세요. 그러면 엔드포인트가 `R2_ACCOUNT_ID`에서 만들어집니다.

부팅할 때 로그에 무해한 `[storage] failed to create bucket` 줄이 하나 나타납니다. R2 토큰은 버킷을 만들 수 없고, 내 버킷은 이미 있기 때문입니다.

### 기타 S3 호환 스토리지
AWS S3, Backblaze B2, DigitalOcean Spaces, Supabase Storage 또는 직접 운영하는 MinIO의 경우:

- `R2_ENDPOINT`를 스토리지의 S3 API URL로 설정하세요.
- 대부분의 셀프 호스팅 서버에서는 `R2_FORCE_PATH_STYLE=true`로 설정하세요.
- `R2_PUBLIC_URL`을 버킷의 공개 URL로 설정하세요.
- 스토리지가 Cloudflare R2나 MinIO가 아니라면 `R2_REGION`을 스토리지의 리전으로 설정하세요.

`R2_REGION`의 기본값은 `auto`입니다. Cloudflare R2 고유의 값이며, MinIO는 이 값을 무시합니다. AWS, DigitalOcean Spaces(`nyc3`, `fra1` 등), 로컬 Supabase(`local`)는 `auto`를 거부합니다. 그러면 모든 요청이 리전을 언급하지 않는 인증 오류나 엔드포인트 오류로 실패합니다.

### 미디어 공개 읽기 설정
방법은 두 가지이며, 대부분의 설치에는 첫 번째 방법만 필요합니다.

1. **버킷 정책.** 기본 방식입니다. 사용자 지정 `R2_ENDPOINT`를 쓰면 앱이 부팅할 때 버킷을 만들고 익명 읽기 정책을 부여합니다. Cloudflare R2에는 필요 없습니다. 공개 버킷 설정이 그 역할을 합니다.
2. **`STORAGE_OBJECT_ACL`을 이용한 객체별 ACL.** 버킷 정책을 거부하는 스토리지용입니다. 대표적인 예가 DigitalOcean Spaces로, 버킷 하나로 범위가 제한된 키의 버킷 정책을 거부합니다. `STORAGE_OBJECT_ACL=public-read`로 설정하면 앱이 쓰는 모든 객체에 그 ACL이 붙습니다.

두 번째 방법이 필요하지 않다면 `STORAGE_OBJECT_ACL`은 비워 두세요. 비어 있으면 ACL 헤더를 보내지 않습니다. 키에 ACL 설정 권한이 없는 스토리지에서 이 값을 설정하면 모든 업로드가 실패합니다. Nodaro는 표준 canned ACL인 `private`, `public-read`, `public-read-write`, `authenticated-read`, `aws-exec-read`, `bucket-owner-read`, `bucket-owner-full-control`을 받으며, 다른 값은 부팅할 때 거부합니다.

## 5. 스택 시작
```bash
docker compose -f docker-compose.community.yml up
```

앱 이미지 `ghcr.io/nodaroai/nodaro-community`는 미리 빌드된 상태로 받아집니다. 첫 부팅은 5~10분 동안 컴파일하는 대신 약 2.4GB를 내려받고, 이후 부팅은 몇 초면 끝납니다. Redis와 `nodaro` 서비스의 로그가 나란히 표시됩니다. 다음 줄이 보이면 API가 실행 중입니다.

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

같은 컨테이너의 웹 서버가 포트 `3000`에서 이를 제공합니다. `http://localhost:3000`을 여세요.

`latest`는 `main` 브랜치를 따라갑니다. 재현 가능한 설치를 위해 릴리스를 고정하려면 `.env`에 `NODARO_IMAGE`를 설정하세요. 예: `NODARO_IMAGE=ghcr.io/nodaroai/nodaro-community:v1.23.0`. [업데이트](https://nodaro.ai/docs/self-hosting/updating)에서 모든 태그를 확인하세요.

대신 소스에서 이미지를 빌드하려면 `docker compose -f docker-compose.community.yml build`를 실행하세요. 코드를 바꿀 때만 필요합니다. 공개 URL, 포트, 도메인, Supabase 키는 컨테이너가 시작할 때 읽히므로, 공개된 이미지도 재시작 후 이 값들을 적용합니다.

## 6. 첫 로그인
앱에서 이메일 주소와 비밀번호로 가입하세요. 로그인 서비스가 사용자를 만들고, Nodaro가 사용자의 프로필을 자동으로 만듭니다. 번들된 스택에서는 확인 이메일을 보내지 않습니다.

Community Edition의 사용자는 제한이 없습니다. 크레딧 원장도 관리자 패널도 없습니다. Business 에디션에서는 다음으로 첫 관리자를 지정하세요. [첫 사용자와 관리자](https://nodaro.ai/docs/self-hosting/first-admin)를 참조하세요.

## 공개 전 설치 보안 강화
Compose 기본값은 로컬 사용을 위한 것이며, 정의상 누구나 알 수 있는 값입니다. 다른 사람이 설치에 접근할 수 있게 하기 전에 다음을 진행하세요.

### 새 인증 키 발급
```bash
node tools/generate-selfhost-keys.mjs >> .env
```

이 스크립트는 `SUPABASE_JWT_SECRET`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`를 출력합니다. 두 키는 시크릿으로 서명되므로, 세 값 모두 같은 실행에서 나온 것이어야 합니다. anon 키를 바꿔도 다시 빌드할 필요가 없습니다. 컨테이너가 실행 시점에 `/config.js`로 브라우저에 전달합니다.

### 새 비밀번호 설정
`POSTGRES_PASSWORD`와 그에 맞는 `DATABASE_URL`을 설정하세요.

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

`R2_ACCESS_KEY_ID`와 `R2_SECRET_ACCESS_KEY`에 새 MinIO 자격 증명도 설정하세요.

데이터베이스는 처음 시작할 때만 내부 역할 비밀번호를 `POSTGRES_PASSWORD`에 맞춥니다. `db-data` 볼륨이 이미 있다면 볼륨을 삭제하거나(데이터도 삭제됩니다), `supabase_admin`으로 역할 비밀번호를 직접 바꾸세요.

### HTTPS로 제공
`PUBLIC_URL`을 실제 `https://` 주소로 설정하고 스택 앞에 리버스 프록시를 두세요. [리버스 프록시와 HTTPS](https://nodaro.ai/docs/self-hosting/reverse-proxy)를 참조하세요.

### 가입 허용 대상 결정
Community Edition은 운영자 한 명을 위한 에디션입니다. 가입 페이지에 접근할 수 있는 사람은 누구나 계정을 만들 수 있고, 로그인한 모든 사용자가 공급자 키를 바꿀 수 있습니다. Business 에디션에서는 관리자만 공급자 키를 관리합니다. [에디션과 서피스 프로필](https://nodaro.ai/docs/self-hosting/editions-and-profiles)을 참조하세요.

모든 변경 사항은 `docker compose -f docker-compose.community.yml up -d`로 적용한 다음 `/setup`을 확인하세요.

## 자주 묻는 질문

### Compose 스택에서는 어떤 .env 파일로 시작해야 하나요?

.env.community.example을 .env로 복사하세요. 이 파일에는 Compose 스택이 쓰는 값만 들어 있습니다. 더 큰 .env.example은 Compose 스택 밖의 설치를 위한 참고용이며, 그 안의 Supabase 자리 표시자 값이 작동하는 기본값을 덮어씁니다.

### INTERNAL_ORCHESTRATOR_SECRET과 NODARO_ENCRYPTION_KEY를 직접 생성해야 하나요?

번들된 Compose 스택에서는 필요 없습니다. 스택이 부팅할 때 둘 다 생성하고, 암호화 키는 app-data 볼륨에 보관합니다. 직접 구성한 오케스트레이션에서는 둘 다 32바이트 랜덤 값을 16진수로 설정하세요. 예를 들어 openssl rand -hex 32로 만들 수 있습니다.

### 번들된 MinIO 대신 Cloudflare R2를 쓰려면 어떻게 하나요?

R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_PUBLIC_URL을 설정하세요. Compose 스택에서는 R2_ENDPOINT를 내 계정의 r2.cloudflarestorage.com 엔드포인트로, R2_FORCE_PATH_STYLE을 false로 함께 설정하세요. 빈 값은 MinIO 기본값으로 대체되기 때문입니다.

### 설치가 제대로 시작되었는지 어떻게 알 수 있나요?

로그에 “server listening on http://0.0.0.0:9000” 줄이 나타날 때까지 기다린 다음 http://localhost:3000/setup을 여세요. 공급자 키를 추가하기 전까지는 공급자 키를 제외한 모든 카드가 초록색이어야 합니다.

### 설치를 네트워크에 공개하기 전에 무엇을 바꿔야 하나요?

node tools/generate-selfhost-keys.mjs로 새 인증 키를 발급하고, 새 POSTGRES_PASSWORD와 그에 맞는 DATABASE_URL, 새 MinIO 자격 증명을 설정하세요. 그리고 PUBLIC_URL을 https 주소로 설정하고 앞단에 리버스 프록시를 두세요.
