custom-domains について
Internet Computer の canister をカスタムドメインで公開するための手順をまとめたスキルです。DNS 設定、所有権確認、登録、TLS 証明書の発行、ドメイン更新を扱います。
主な機能
- 01DNS レコードの設定
- 02ドメイン所有権ファイルの配置
- 03ドメインの検証と登録
- 04証明書と登録状態の確認
- 05ドメインの更新と削除
活用シーン
- 01ブランドドメインで canister を公開
- 02カスタムドメイン登録の失敗を調査
- 03別の canister へのドメイン移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dfinity/ icskills --skill custom-domainsインストール後、Claude Code で/custom-domainsを入力して読み込まれたか確認します。
詳細
カスタムドメイン
概要
デフォルトでは、キャニスターには <canister-id>.icp.net でアクセスできます。カスタムドメインサービスを使うと、独自のドメイン(例:yourdomain.com)で任意のキャニスターを提供できます。DNS を設定し、ドメイン所有権ファイルをキャニスターにデプロイして、REST API 経由で登録します。HTTP ゲートウェイは、TLS 証明書の発行と更新、ルーティングを自動的に処理します。
カスタムドメインは境界ノードレベルで動作し、DNS を介してドメインを任意のキャニスター ID にマッピングします。これは、アセットキャニスターだけでなく、HTTP 経由で /.well-known/ic-domains を提供できるあらゆるキャニスターで機能します。これには、アセットキャニスター、Juno サテライト、http_request を実装するカスタムキャニスターが含まれます。
前提条件
- いずれかのレジストラから登録したドメイン(例:Namecheap、GoDaddy、Cloudflare)
- そのドメインの DNS レコードを編集する権限
- HTTP 経由で
/.well-known/ic-domainsを提供するデプロイ済みキャニスター(アセットキャニスター、Juno サテライト、またはhttp_requestを実装する任意のキャニスター) - 登録 API 呼び出し用の
curl jq(任意。JSON レスポンスの整形用)
セットアップを壊すよくあるミス
-
DNS プロバイダーの SSL/TLS を無効にしていない。 Cloudflare などのプロバイダーでは、デフォルトで Universal SSL が有効になっています。これは、IC が証明書を発行するために使用する ACME チャレンジを妨げ、証明書の更新を阻止することがあります。登録前に、DNS プロバイダーが提供する証明書/SSL/TLS の機能を無効にしてください。
-
apex ドメインに CNAME を設定している。 多くの DNS プロバイダーでは、apex(サブドメインのない
example.comなど)に CNAME レコードを設定できません。プロバイダーが対応している場合は、ANAME または ALIAS レコードタイプ(CNAME フラットニング)を使用してください。対応していない場合は、www.example.comなどのサブドメインを使用してください。 -
_acme-challengeCNAME がない。_acme-challenge.CUSTOM_DOMAINが_acme-challenge.CUSTOM_DOMAIN.icp2.ioを指していないと、HTTP ゲートウェイは TLS 証明書を取得できません。登録は失敗します。 -
_canister-idに複数の TXT レコードがある。_canister-id.CUSTOM_DOMAINに複数の TXT レコードが存在すると、登録は失敗します。キャニスター ID を含むレコードを 1 つだけ残してください。 -
.well-known/ic-domainsファイルを忘れている。 キャニスターは、カスタムドメインを記載した/.well-known/ic-domainsを提供する必要があります。このファイルがないと、登録時のドメイン所有権の確認に失敗します。 -
DNS プロバイダーの古い
_acme-challengeTXT レコードが残っている。 プロバイダーが以前に行った ACME チャレンジによって、ダッシュボードには表示されない TXT レコードが_acme-challenge.CUSTOM_DOMAINに残っていることがあります。これらは IC の ACME フローと競合します。プロバイダーが提供するすべての TLS 機能を無効にして、レコードを削除してください。dig TXT _acme-challenge.CUSTOM_DOMAINで確認できます。 -
ドメインを明示的に登録していない。 DNS の設定だけでは不十分です。登録を開始するには、
POST /custom-domains/v1/CUSTOM_DOMAINを呼び出す必要があります。自動的には登録されません。 -
HttpAgentのhostにカスタムドメインを設定している。hostはキャニスター呼び出しの送信先となる API エンドポイント であり、フロントエンドの提供元ドメインではありません。カスタムドメインは HTTP ゲートウェイであり、/api/v2を提供しません。そのため、hostにカスタムドメイン(またはwindow.location.origin)を指定すると、呼び出しは失敗します。hostを設定する必要はありません。最近の@icp-sdk/coreのHttpAgentは、カスタムドメイン上でhostが省略されている場合、https://icp-api.io(メインネット API 境界ノード)に解決します。未設定のままにするか、明示的にhttps://icp-api.ioを指定してください。ゲートウェイドメインは指定しないでください。 -
すでにユーザーがいるアプリにカスタムドメインを追加している。 II プリンシパルはオリジンごとに異なるため、新しいドメインでは、再訪するすべてのユーザーにまったく新しいアカウントが割り当てられます。派生元を固定してください。カスタムドメインに置き換えられることのないキャニスターアドレスを使い、ドメインが有効になる 前に
.well-known/ii-alternative-originsにそのドメインを記載してください。すでにサインインを収集したドメインは、アカウントを孤立させずに転用することはできません。internet-identityスキルを参照してください。
実装
ステップ 1:DNS レコードを設定する
3 つの DNS レコードを追加します(CUSTOM_DOMAIN をドメインに置き換えてください。例:app.example.com)。
| レコードタイプ | ホスト | 値 |
|---|---|---|
| CNAME | CUSTOM_DOMAIN | CUSTOM_DOMAIN.icp1.io |
| TXT | _canister-id.CUSTOM_DOMAIN | お使いの Canister ID(例:hwvjt-wqaaa-aaaam-qadra-cai) |
| CNAME | _acme-challenge.CUSTOM_DOMAIN | _acme-challenge.CUSTOM_DOMAIN.icp2.io |
一部の DNS プロバイダーでは、メインドメインのサフィックスが省略されます。このようなプロバイダーで app.example.com を使用する場合:
app.example.comの代わりにapp_canister-id.app.example.comの代わりに_canister-id.app_acme-challenge.app.example.comの代わりに_acme-challenge.app
CNAME に対応していないネイキッドドメインでは、プロバイダーの ANAME または ALIAS レコードタイプを使用して、CUSTOM_DOMAIN.icp1.io を指定してください。
ステップ 2:ic-domains ファイルを作成する
Canister は HTTP 経由で /.well-known/ic-domains を配信する必要があります。このファイルを作成し、カスタムドメインを 1 行に 1 つずつ記載してください。
app.example.com
www.example.com
フロントエンド Canister のユーザー向け: .well-known/ を public/ ディレクトリ(Vite プロジェクトの場合)内、またはソースファイルと同じ場所に配置し、dir に含まれるようにしてください。推奨の @dfinity/static-site レシピでは、.well-known/ は自動的にアップロードされます(設定は不要です)。従来の @dfinity/asset-canister レシピでは、非表示ディレクトリがデプロイされるように .ic-assets.json5 に { "match": ".well-known", "ignore": false } が含まれていることを確認してください。ファイルの配置について詳しくは、static-site スキルを参照してください。
カスタム http_request Canister 向け: HTTP リクエストハンドラーから /.well-known/ic-domains にファイルの内容を直接配信してください。
ステップ 3:デプロイする
Canister をデプロイし、https://<canister-id>.icp.net/.well-known/ic-domains で /.well-known/ic-domains にアクセスできるようにします。
ステップ 4:検証する
登録する前に、DNS レコードと Canister の設定を確認してください。
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN/validate" | jq
成功時の応答:
{
"status": "success",
"message": "Domain is eligible for registration: DNS records are valid and canister ownership is verified",
"data": {
"domain": "CUSTOM_DOMAIN",
"canister_id": "CANISTER_ID",
"validation_status": "valid"
}
}
検証に失敗した場合によくあるエラーと対処方法:
| エラー | 対処方法 |
|---|---|
| DNS CNAME レコードがありません | _acme-challenge.CUSTOM_DOMAIN.icp2.io を指定する _acme-challenge CNAME を追加します |
| DNS TXT レコードがありません | Canister ID を値とする _canister-id TXT レコードを追加します |
| DNS TXT レコードが無効です | TXT の値が有効な Canister ID であることを確認します |
| DNS TXT レコードが複数あります | 重複する _canister-id TXT レコードを削除し、1 つだけ残します |
| 登録済みドメインを取得できませんでした | .well-known/ic-domains が Canister にデプロイされ、配信されていることを確認します |
| ドメインがリストにありません | ic-domains ファイルにドメインを追加し、再デプロイします |
ステップ 5:登録する
curl -sL -X POST "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
成功時の応答:
{
"status": "success",
"message": "Domain registration request accepted and may take a few minutes to process",
"data": {
"domain": "CUSTOM_DOMAIN",
"canister_id": "CANISTER_ID"
}
}
ステップ 6:証明書のプロビジョニングを待つ
registration_status が registered になるまでポーリングします。
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
ステータスの値:registering → registered(成功)、または failed(エラーメッセージを確認してください)。
registered になった後、すべての HTTP ゲートウェイに反映されるまでさらに数分待ってからテストしてください。
カスタムドメインを更新する
既存のカスタムドメインの接続先を別の Canister に変更するには:
_canister-idTXT レコードを新しい Canister ID に更新します。- サービスに通知します。
curl -sL -X PATCH "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
- ステータスを確認します。
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
カスタムドメインを削除する
- DNS から
_canister-idTXT レコードと_acme-challengeCNAME を削除します。 - サービスに通知します。
curl -sL -X DELETE "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
- 削除を確認します(404 が返るはずです)。
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
HttpAgent の設定
カスタムドメインで配信されるフロントエンドも、Canister への呼び出しには配信元のドメインではなく、メインネットの API 境界ノード(https://icp-api.io)を使用します。HttpAgent の host はこの API エンドポイントであり、フロントエンドのオリジンではありません。カスタムドメインは HTTP ゲートウェイであり、/api/v2 は配信しません。
host を設定する必要はありません。省略すると、最近の @icp-sdk/core の HttpAgent は、カスタムドメイン(および icp.net)で https://icp-api.io を解決先として使用します。host にカスタムドメインや window.location.origin を指定しないでください。これらはゲートウェイであるため、呼び出しに失敗します。
import { HttpAgent } from "@icp-sdk/core/agent";
// host omitted on a custom domain → resolves to https://icp-api.io
const agent = await HttpAgent.create();
// equivalent, explicit:
const agentExplicit = await HttpAgent.create({ host: "https://icp-api.io" });
デプロイとテスト
# 1. Deploy your canister with the ic-domains file served at /.well-known/ic-domains
# 2. Validate DNS + canister config
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com/validate" | jq
# 3. Register
curl -sL -X POST "https://icp.net/custom-domains/v1/yourdomain.com" | jq
# 4. Poll until registered
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq
動作を確認する
# 1. Verify DNS records
dig CNAME yourdomain.com
# Expected: yourdomain.com. CNAME yourdomain.com.icp1.io.
dig TXT _canister-id.yourdomain.com
# Expected: "<your-canister-id>"
dig CNAME _acme-challenge.yourdomain.com
# Expected: _acme-challenge.yourdomain.com. CNAME _acme-challenge.yourdomain.com.icp2.io.
# 2. Verify ic-domains file is served by the canister
curl -sL "https://<canister-id>.icp.net/.well-known/ic-domains"
# Expected: your domain listed
# 3. Verify registration status is "registered"
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq '.data.registration_status'
# Expected: "registered"
# 4. Verify the custom domain serves your canister
curl -sI "https://yourdomain.com"
# Expected: HTTP/2 200
# 5. Verify no stale ACME TXT records
dig TXT _acme-challenge.yourdomain.com
# Expected: no TXT records (only the CNAME)