# NodeSrv Node.js WEBアプリを個別に開発し、既存の [Dokploy](https://dokploy.com/) 環境(AWS Lightsail上でセルフホスト稼働中)へデプロイ・運用するためのプロジェクト。Synology NASは開発機のローカルストレージ用途であり、アプリの実行基盤ではない。 ## 構成 ``` NodeSrv/ apps/ _template/ # 新規アプリのひな形(コピーして使う) / # 実際の各アプリ deploy/ dokploy.env.example # Dokploy接続情報のテンプレート(実値はdokploy.envに。gitignore済み) AUTH_SECRETS.md # 認証基盤の機密情報(Client Secret等)。gitignore済み、リポジトリには含まれない ``` 1アプリ = 1フォルダ = 1 Dokploy Application/Compose リソース。アプリ同士は完全に独立しており、片方の開発・再デプロイがもう片方に影響しない。 ## 運用方針 - デプロイ先: Dokploy(AWS Lightsail上のセルフホストPaaS) - Gitソース: 自前ホストのGitea(`https://git73.next-hd.net/mygit-admin/NodeSrv.git`)経由でビルド - 公開ドメイン: `*.apps.next-hd.net` 配下(ワイルドカードAレコード登録済み。新規アプリは個別のDNS登録不要、`.apps.next-hd.net`で命名しデプロイするだけでよい) - TLS証明書: Let's Encrypt(Traefikのcertresolverで自動発行、HTTP-01方式。新規サブドメインも初回アクセス時に自動発行される) - 認証: **デフォルトは認証なしで公開。** 認証が必要なアプリのみ、下記「認証ゲートウェイ」章の手順でオプトインする(全アプリ共通の必須事項ではない) 新規アプリはまず認証なしの最小構成でデプロイし、稼働・疎通を確認してから、必要な場合のみ認証を追加する段階的な進め方を基本とする。 `dokploy45`/`auth91`/`gate66`等の既存インフラは `next-hd.net` 直下(ランダム数字命名)のまま。個別アプリ用の `apps.next-hd.net` サブゾーンとは名前空間を分離している。 ## 新規アプリの作り方 1. `apps/_template` を `apps/<新アプリ名>` としてコピー 2. `package.json` の `name` を変更、必要な依存関係を追加 3. ローカルで開発(詳細は `apps/_template/README.md` および各アプリの README を参照) 4. `docker-compose.yml` 内の以下3つを実際の値に置換(テンプレートのままで認証なし公開ができる。下記「ポートを`ports:`で直接ホスト公開しない」ルールのみ厳守) - `your-app-name`: Dockerサービス名・Traefikルーター識別子(内部識別子。外部には露出しない) - `yourapp.apps.next-hd.net`: 公開ドメイン(先頭ラベルは`your-app-name`と一致させる必要はなく、自由に決めてよい) - ポート番号 5. Giteaへpush → Dokployに登録しデプロイ([.claude/skills/dokploy-webapp/SKILL.md](.claude/skills/dokploy-webapp/SKILL.md) 参照) 6. 認証が必要な場合のみ、下記「認証ゲートウェイ」章の手順を追加で適用 ### 共通ルール - **Compose の Provider は必ず「Raw」を選ぶ**(Dokploy標準の「Domains」タブは使わない)。Gitea自体もこの方式で稼働実績があり、後から認証ミドルウェアを追加する場合もシームレスに拡張できるため、無認証アプリでも統一して使う - **ポートを `ports:` で直接ホスト公開しない。** `ports: - "3000:3000"` のようにホストへポートを直接公開すると、Traefikを経由しない直アクセス経路が生まれる(将来認証を追加した場合の迂回経路にもなる)。**必ず `expose` を使い、`dokploy-network` 経由でのみ到達可能にすること。** ## 認証ゲートウェイ(oauth2-proxy + Keycloak)── 任意オプション **デフォルトでは適用しない。** 特定のアプリに認証を必須化したい場合のみ、以下の手順で追加する。 アプリ側でログイン画面やOIDC処理を自前実装する必要は**ない**。認証はTraefik層(oauth2-proxy経由)で強制される。 ### 前提となる構成 - IdP: Keycloak(`https://auth91.next-hd.net`、realm: `nexthd`) - 認証ゲートウェイ: oauth2-proxy(`https://gate66.next-hd.net`、Dokploy上の`app-gateway`プロジェクトで稼働) - Cookieドメイン: `.next-hd.net`(この配下のサブドメイン間でログインセッションが共有される=一度ログインすれば他アプリも再ログイン不要) - Dokploy管理画面: `https://dokploy45.next-hd.net` ### 追加ルール #### 1. ドメインは必ず `next-hd.net` のサブドメインにする Cookie共有の前提が `.next-hd.net` のため、別ドメイン・別TLD(例: `next-hd.co.jp`側や外部ドメイン)で公開するとSSOが機能しない。 #### 2. `_template` の compose に認証ミドルウェアを1行追加する `apps/_template/docker-compose.yml`(共通ルール通りRaw provider・手書きラベル)に、`...-websecure` ルーターへ以下の1行を追加するだけでよい。 ```yaml - traefik.http.routers.your-app-name-websecure.middlewares=oauth-errors@file,oauth-auth@file ``` `web`(HTTPリダイレクト用)ルーターには追加しないこと(HTTP→HTTPSへの転送のみを担当させる)。 > 補足: 複数アプリで1つのHostを共有し `/` パスプレフィックスで振り分ける方式(DNS登録の手間を減らす狙い)を検証中。確定後、この節に追記する。 ### ログインユーザー情報の取得方法 認証済みリクエストには、oauth2-proxyが以下のヘッダーを付与してアプリに転送する。 | ヘッダー名 | 内容 | |---|---| | `X-Auth-Request-User` | Keycloak上のユーザー名 | | `X-Auth-Request-Email` | メールアドレス | **Node.js例:** ```js const userEmail = req.headers['x-auth-request-email']; const userName = req.headers['x-auth-request-user']; ``` **Python (Flask) 例:** ```python user_email = request.headers.get('X-Auth-Request-Email') user_name = request.headers.get('X-Auth-Request-User') ``` これらのヘッダーは、Traefik/oauth2-proxyを正しく通過したリクエストにのみ付与される。アプリ側で改めてJWT検証等を行う必要はない(ただし本番運用でセキュリティ要件が上がった場合は、アプリ側でもヘッダーの信頼境界を見直すこと。ポート直公開さえしていなければTraefik経由以外の到達手段がないため、現状はこの前提で問題ない)。 ### ローカル開発時の注意 `localhost` でのローカル起動時はTraefik/oauth2-proxyを経由しないため、上記ヘッダーは存在しない。ローカル開発では以下のようなフォールバックを入れておくと開発しやすい。 ```js const userEmail = req.headers['x-auth-request-email'] || 'dev-local@next-hd.co.jp'; ``` ### 機密情報の扱い - Keycloakのクライアントシークレットやoauth2-proxyのcookie secret、SendGrid APIキー等は、このリポジトリやアプリのコード・`.env`には**書かない**。値は `AUTH_SECRETS.md`(gitignore済み・リポジトリ非追跡)でのみ管理する。 - アプリ自体は認証ゲートウェイの設定を一切知る必要がない(ヘッダーを読むだけ)。 ### トラブルシューティング | 事象 | 確認ポイント | |---|---| | ずっと401が返る/画面が真っ白 | `oauth-errors@file` がミドルウェアの1番目に指定されているか(`oauth-errors@file,oauth-auth@file` の順序を保つ) | | ログインループになる | Cookieドメインが `.next-hd.net` になっているか、アプリのドメインが `next-hd.net` 配下か | | 別アプリで再ログインを求められる | 初回ログインからのCookie有効期限切れ、またはドメインが`next-hd.net`配下でない可能性 | | X-Auth-Request-* ヘッダーが空 | `ports:` でポートを直接ホスト公開し、Traefikを迂回してアクセスしていないか確認 | ## インフラ環境詳細 対象サーバー: Dokploy (`dokploy45.next-hd.net`) / Keycloak (`auth91.next-hd.net`) ### mail-infra (postfix-relay) Dokployプロジェクト「mail-infra」→サービス「postfix-relay」(Docker Compose、production環境) - コンテナ名: `mailinfra-postfixrelay-jodrpy` - 内部ホスト名: `postfix-relay`(dokploy-networkの他コンテナから到達可能) - リッスンポート: 587(expose、外部公開なし) - ネットワーク: `dokploy-network`(external) - Compose環境変数: `ALLOWED_SENDER_DOMAINS=next-hd.co.jp`、`RELAYHOST="[smtp.sendgrid.net]:587"`、`RELAYHOST_USERNAME=apikey`、`RELAYHOST_PASSWORD=`、`POSTFIX_smtp_tls_security_level=encrypt`、`POSTFIX_smtpd_tls_security_level=none` ### Dokploy通知設定(Settings > Notifications) - Name: `postfix-relay` - SMTP Server: `postfix-relay` / Port: `587` - Username: `noreply`(ダミー値、認証は使用されません) - Password: ダミー値(画面上マスク済み、実際の認証には使われません) - From Address / To Address: 設定値が `dokploy-admin@next-hd.co.jp` にリセットされる挙動あり(編集ダイアログを開くたびに表示がリセットされるUI上の癖の可能性)。正しい値(From: `no-reply@next-hd.co.jp`、To: `kenichiro.nogi@next-hd.co.jp`)を維持したい場合は保存前に必ず確認。 ### Keycloak メール送信設定(nexthd レルム) Realm settings > Email タブ - Host: `postfix-relay` / Port: `587` - SSL/StartTLS: OFF、Authentication: OFF - From: `keycloak-admin@next-hd.co.jp` ### Keycloak クライアント「app-gateway」(nexthd レルム) | 項目 | 値 | |---|---| | Client ID | `app-gateway` | | Name | 個別アプリ認証ゲートウェイ | | Client type | OpenID Connect / Confidential(Client authentication ON) | | Authentication flow | Standard flow のみ | | Valid redirect URIs | `https://gate66.next-hd.net/oauth2/callback` | | Valid post logout redirect URIs | `https://gate66.next-hd.net/*` | | Web origins | `https://gate66.next-hd.net` | | Client Secret | `AUTH_SECRETS.md`参照 | | Issuer URL | `https://auth91.next-hd.net/realms/nexthd` | ### Dokploy「app-gateway」プロジェクト / oauth2-proxy サービス - プロジェクト: `app-gateway`(production環境) - サービス: `oauth2-proxy`(コンテナ名: `appgateway-oauth2proxy-y7xl6r`) - 公開ドメイン: `https://gate66.next-hd.net` (Container Port: 4180、HTTPS/Let's Encrypt) - Compose環境変数: `OAUTH2_PROXY_PROVIDER=keycloak-oidc`、`OAUTH2_PROXY_OIDC_ISSUER_URL=https://auth91.next-hd.net/realms/nexthd`、`OAUTH2_PROXY_CLIENT_ID=app-gateway`、`OAUTH2_PROXY_CLIENT_SECRET=`、`OAUTH2_PROXY_COOKIE_SECRET=`、`OAUTH2_PROXY_COOKIE_DOMAINS=.next-hd.net`、`OAUTH2_PROXY_WHITELIST_DOMAINS=.next-hd.net`、`OAUTH2_PROXY_REDIRECT_URL=https://gate66.next-hd.net/oauth2/callback`、`OAUTH2_PROXY_EMAIL_DOMAINS="*"`、`OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180`、`OAUTH2_PROXY_UPSTREAMS=static://202`、`OAUTH2_PROXY_REVERSE_PROXY="true"`、`OAUTH2_PROXY_SET_XAUTHREQUEST="true"`、`OAUTH2_PROXY_SKIP_PROVIDER_BUTTON="true"` ### Traefikミドルウェア(`/etc/dokploy/traefik/dynamic/middlewares.yml`) 既存の `redirect-to-https` に加え、以下を追記済み(他アプリのルーターに `oauth-auth` ミドルウェアを適用することで認証を強制できる)。 ```yaml http: middlewares: redirect-to-https: redirectScheme: scheme: https permanent: true oauth-auth: forwardAuth: address: https://gate66.next-hd.net/oauth2/auth trustForwardHeader: true authResponseHeaders: - X-Auth-Request-User - X-Auth-Request-Email oauth-errors: errors: status: - "401-403" service: oauth-backend-svc query: /?rd={url} services: oauth-backend-svc: loadBalancer: passHostHeader: false servers: - url: https://auth-redirect.apps.next-hd.net ``` **`statusRewrites`は使っていない**(2026-07-25判明)。Traefikの`errors`ミドルウェアは、リダイレクト先のHTMLボディは反映するが、レスポンスのステータスコード自体は元のエラーコード(401)のまま保持する既知の制約がある([Traefik issue #10720](https://github.com/traefik/traefik/issues/10720))。ブラウザはステータスコードに関わらずHTMLボディをレンダリングするため、代わりに`apps/auth-redirect`(`meta refresh`で`gate66.next-hd.net/oauth2/sign_in`へ自動遷移する薄いページ)を用意し、`oauth-errors`の転送先をここに向けることで自動リダイレクトを実現している。`auth-redirect`自体は`webapps`project配下の通常のCompose(composeId: `2OUXZISkrQ2Wk5uG86hcz`)としてデプロイ済み。デバッグ用に`auth-redirect.apps.next-hd.net`でも単体公開している(認証ミドルウェアなし)。 ### Dokploy CLI(claude code for vscode からのデプロイ用) - インストール: `npm install -g @dokploy/cli` - 認証: `dokploy auth -u https://dokploy45.next-hd.net -t `(トークン値は`AUTH_SECRETS.md`または発行時にコピーしたクリップボードを参照。チャットには貼らない) - Compose作成・デプロイの具体的なコマンド手順は [.claude/skills/dokploy-webapp/SKILL.md](.claude/skills/dokploy-webapp/SKILL.md) 参照(新規Compose作成含め実地検証済み)。CLIのGET系コマンドは実装バグにより全滅するため、状態確認はSSH直接調査に頼る(同SKILL.md参照)。 ## アプリポータル(apps/app-portal) `webapps` project配下の複数アプリを横断して起動/停止/再起動/バッチ手動実行/ログ確認を行う集中管理ポータル。設計の経緯・アーキテクチャは `docs/superpowers/specs/2026-07-25-app-portal-design.md`、実装計画は `docs/superpowers/plans/2026-07-25-app-portal.md` 参照。 - 公開ドメイン: `https://portal.apps.next-hd.net`(認証ゲートウェイ必須 + アプリ層allowlist二段防御) - 各アプリをポータルに登録するには、`.env.example`のPORTAL_*変数(`apps/_template/README.md`参照)をDokploy Environment画面で設定する - Dokploy tRPC APIを自前クライアント(`apps/app-portal/src/dokployClient.js`)で直接呼び出している。CLIのGET系コマンドが実装バグで全滅する問題(上記「Dokploy CLI」章参照)の回避策の実例でもある ## 参考情報 - 認証ゲートウェイ: `https://gate66.next-hd.net`(Dokploy `app-gateway` プロジェクト、oauth2-proxyサービス) - Keycloak管理コンソール: `https://auth91.next-hd.net/admin/master/console/`(realm: `nexthd`) - Keycloakクライアント: `app-gateway`(Client Credentials方式、Standard flowのみ有効) - Dokploy管理画面: `https://dokploy45.next-hd.net`