Publish a static site (SSG output, SPA, LP, etc.) to Firebase Hosting,main Build up to the state where it is automatically deployed on push to.
The default publishing destination is the Spark plan (no billing account linked). Since no payment method exists, no charges will be incurred even if access concentrates.
Prerequisites
Required:
gcloudCLI (if not installed,brew install --cask google-cloud-sdk。PATH は/opt/homebrew/share/google-cloud-sdk/bin)ghCLI (authenticated)- Node.js(
npx firebase-toolsuse).firebase-toolsRun with a pinned version (FIREBASE_TOOLS_VERSION). For the value and update procedure, see the section "Pinning and updating the firebase-tools version" - The target repository must be on GitHub
${SA_ID}@${PROJECT_ID}.iam.gserviceaccount.com(defaultSA_ID=github-actions-hosting) must be a dedicated service account created and managed by this script, and must not be shared with other uses (the description is occupied by this script as an issuance record). Whether it is managed is determined not by email matching but by the management evidence (marker) in the description. For a newly created SA, set the evidence immediately after creation, and for an existing SA with no evidence, do not proceed to overwriting the description or managing keys, but stop fail-closed (only when taking it over as dedicated to this script, explicitly adopt it withADOPT_EXISTING_SA=true). The deletion targets of automatic key rotation (described later) are limited to only "keys recorded in this SA's description (the issuance record on the GCP side)"; keys not in the record are not deleted and are only listed (fail-safe)
Conditions for the target site:
- It must be a completely static site(no server processing, API, or DB). If dynamic processing is needed, consider Cloud Run etc. (see "Reasons for not choosing other services" below)
- Running one build command completes the output directory (
dist/etc.)
About execution in a sandbox environment: bootstrap-firebase.sh は gcloud auth login Because it requires browser authentication, GCP/Firebase API calls,gh secret set and other authentication operations over the network, it cannot be executed in a sandbox (under network restrictions). Please execute it on an authenticated local terminal or on CI.firebase.json Creating and local verification (Step 3-4) do not require the network, so they can be executed even in a sandbox.
What to confirm with the user first
1. Acceptance of the Firebase Terms of Service (once per Google account, can only be done in the console)
If not accepted, the subsequent addFirebase が 403 PERMISSION_DENIED will fail.Please complete it first. This is a specification constraint explicitly stated in the official documentation.
"Accepting the Firebase Terms is not possible using the Firebase CLI, REST API, or Terraform. It can only be done using the Firebase console." — Get started with Firebase using an existing Google Cloud project
https://console.firebase.google.com/ Open it, create any one project, or add Firebase to an existing project and agree to the terms.
This 403 returns the same message as insufficient IAM permissions (The caller does not have permission), so it cannot be distinguished. It can be isolated as follows.
curl -sS -X POST "https://cloudresourcemanager.googleapis.com/v1/projects/<PROJECT_ID>:testIamPermissions" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "x-goog-user-project: <PROJECT_ID>" -H "Content-Type: application/json" \
-d '{"permissions":["firebase.projects.update","resourcemanager.projects.get","serviceusage.services.enable","serviceusage.services.get"]}'
When all four are returned but addFirebase is 403, the cause is unaccepted terms.
2. Plan (Spark / Blaze)
The default is Spark. If your top priority is "not being charged without your consent," this is the only option.
| Spark | Blaze | |
|---|---|---|
| Billing | No payment method exists (structurally $0) | Usage-based billing for overage beyond the free tier ($0.15/GB) |
| Free tier | Storage 10 GB / transfer 10 GB per month | Same |
| If the quota is exceeded | Site disabled after a short grace period,not restored until the next month | It does not stop, butthere is no cap on the billed amount |
Blaze has no means to enforce a cap. It has been confirmed that none of the three can be used.
- Budget alerts do not stop it — officially "budgets and budget alerts do not cap your usage or charges"
- Cloud Spend Caps (hard caps) only support Gemini API / Gemini Enterprise Agent Platform / Cloud Run / Cloud Run functions, andFirebase Hosting is not covered
- The standard workaround of budget notification → Pub/Sub → Cloud Functions to disable billing, once disabled, reverts to the Spark equivalent = in the end the site stops, and the detection lag portion is billed
Even with malicious mass access (denial of wallet), the damage is asymmetric. Spark limits it to $0 in monetary damage and "stopping for that month" (the loss ceiling is fixed), while Blaze continues billing and does not stop the site, continuing to serve the attacker.
3. Site ID
The public URL is determined by the Hosting site ID (not the project ID). Since multiple sites can be placed in one project, the two are independent. You can operate by placing a site named after your idea under a general-purpose project.
- Project
myorg-notes+ sitenotes→https://notes.web.app
Site IDs are unique across all of Firebase. If one is already taken, choose a different ID.
4. Whether to use a custom domain
<site>.web.app If so, it is completely $0 and no DNS operations are required. If using a custom domain,registering DNS records remains a UI operation on the registrar side. If you use Cloud DNS, you can manage it in code, but it is a paid service at $0.20/zone/month and requires a billing account, sothe Spark guarantee is lost。
If you plan to build up SEO, decide early. Migrating a domain requires no code changes (environment variable conversion described later), but the reputation accumulated with search engines is reset.
Procedure
Step 1: Place the bootstrap script and run it
scripts/bootstrap-firebase.sh in the target repository's tools/bootstrap-firebase.sh and rewrite the placeholder at the beginning. The deploy workflow and re-runs assume this path.
PROJECT_ID=__PROJECT_ID__ # 例: myorg-<project>
SITE_ID=__SITE_ID__ # 例: <idea>。公開 URL は https://<SITE_ID>.web.app
GITHUB_REPO=__OWNER__/__REPO__ # Secret / 変数の登録先
If you run it without rewriting, it will stop at the safety valve at the beginning. PROJECT_ID If the project for does not exist,create a new onebecause of the behavior, running it with the placeholder still in place will create an unintended project (I actually did this).
gcloud auth login # ブラウザ認証(初回のみ・自動化不可)
bash tools/bootstrap-firebase.sh # 冪等。再実行しても安全
What the script does:
- Check/create a GCP project (not linking a billing account = fixed to Spark)し、
billingEnabledがFalseIf it cannot be determined as stop fail-closed(do not assume it is unlinked). Only when intentionally proceeding with Blaze,ALLOW_BLAZE=trueattach and explicitly approve firebase/firebasehosting/cloudresourcemanager/serviceusage/iamthe API- Add Firebase via the Firebase Management API.
addFirebaseIf is 403, the required 4 permissions (firebase.projects.update/resourcemanager.projects.get/serviceusage.services.enable/serviceusage.services.get)AllをtestIamPermissionsare verified in practice, and if any are missing, list the missing permissions, and if all are present, guide toward the possibility that the terms have not been accepted (do not assume). Create a site via the Hosting API. If creation returns 409 (already exists), treat it as idempotent success only when the site's existence under your own project can be confirmed; if another project has already acquired the same site ID, request a different ID and stop - Create a service account for CI andminimum rolesgrant (
roles/firebasehosting.admin+roles/serviceusage.apiKeysViewer) - Issue a new key → append the key ID to the issuance record (the description of the dedicated SA. This is a record on the GCP side, and rewriting it requires GCP IAM write permission) →
gh secret set FIREBASE_SERVICE_ACCOUNT→ after successful registration,delete only the old USER_MANAGED keys listed in the issuance record(generation rotation to counter the limit of 10). Since SA keys themselves have no metadata and it cannot be verified from the key alone whether it was issued by this script, limit the basis for deletion permission to the issuance record on the GCP side. Information editable on the GitHub side (such as Actions variables) is not used as a basis for deletion, because tampering can propagate through the executor's GCP permissions to revoking valid keys. Keys not in the record (possibly manually issued or issued by another tool) are not deleted but listed for the user to decide (fail-safe). Appending to the record is done before Secret registration, and deletion of old keys is done after successful registration; furthermore, if the process terminates abnormally before Secret registration completes, delete the key issued this time itself and roll back (because this key is definitely the property of this execution. This prevents unusable valid keys from accumulating on every failure). If the number of keys has reached the GCP limit (10 USER_MANAGED), first delete only the old keys in the issuance record (excluding the last recorded key = the key most likely pointed to by the current Secret) to make room, and if room cannot be made using the keys in the record, stop without deleting and guide manual cleanup. If you want to disable it, specifyROTATE_EXISTING_KEYS=false(this also applies to pre-deletion when the limit is reached; in that case, stop without deleting and guide manual cleanup). Finally,Delete the local key file(also on abnormal termination via trap) gh variable set FIREBASE_PROJECT_ID/FIREBASE_SITE_ID、.firebasercgenerate
For adding Firebase and creating a site, call the REST API directly with a gcloud token instead of the firebase CLI. This is because the firebase CLI has separate credentials from gcloud, so using the CLI adds one more browser authentication step. For API calls, the x-goog-user-project: <PROJECT_ID> header is required. gcloud user credentials do not have a quota billing account, and without this, gcloud's own client project is treated as the consumer and 403 SERVICE_DISABLED it will be.
Roles that are not attached also have meaning. Since we assume neither Auth nor Cloud Run rewrites will be used, roles/firebaseauth.admin / roles/run.viewer is not attached. As a result, at deploy time Unable to add channel domain to Firebase Auth a warning appears, butharmlessis.
Step 2: Make the base URL a build-time environment variable
Generate the absolute URLs for canonical, OGP, sitemap, and JSON-LD from a single place, anddo not hardcode fixed values. This makes code changes unnecessary even when migrating to a custom domain.
Rust example:
pub const BASE_URL: &str = match option_env!("SITE_BASE_URL") {
Some(url) => url,
None => "https://<site-id>.web.app",
};
// build.rs — これがないと target/ を再利用する self-hosted ランナーで
// 「環境変数を変えたのに古い canonical が出力され続ける」状態になる
fn main() {
println!("cargo:rerun-if-env-changed=SITE_BASE_URL");
}
For other stacks as well, similarly,the build cache detects changes to environment variables, please confirm this.
Step 3: Create firebase.json
{
"hosting": {
"site": "<site-id>",
"public": "dist",
"trailingSlash": false,
"cleanUrls": false,
"ignore": ["firebase.json", "**/.*"],
"headers": [
{
"source": "**",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=3600" }]
},
{
"source": "/sw.js",
"headers": [{ "key": "Cache-Control", "value": "no-cache" }]
},
{
"source": "/static/**",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
]
}
]
},
"emulators": {
"hosting": { "port": 5002 },
"singleProjectMode": true
}
}
Points to follow:
trailingSlash: false— Required if you output canonical without a trailing slash. The default is/about/normalizes to this, so if left as is, canonical and the actual URL will be off by one 301 on every page. Conversely, if canonical has a trailing slash, no specification is neededheadersthe later entry wins. catch-all**Place this first and put individual specifications after it. Also, HTML page request paths without extensions(/about), so**/*.htmldoes not match. Catch it with a catch-all- Do not add
immutableto assets whose filenames do not contain a content hash. Updates will never be delivered permanently.max-age=0, must-revalidateIn that case, a 304 is sufficient and almost no transfer volume is consumed - If you use a Service Worker,
/sw.jsはno-cache。 If the SW script body itself is cached, cache generation management stops working, and users get stuck on an old build trailingSlash: falseknown infinite redirect(superstatic#235)—X.htmlとX/index.htmloccurs when these coexist. Check that there are no conflicts in the output- On macOS, port 5000 is occupied by ControlCenter (AirPlay Receiver), so change the emulator port
Step 4: Verify the serving configuration locally (no GCP account required)
firebase emulators:start はthe same superstatic engine as productionで firebase.json interprets this. You can finalize it here before deployment.
# firebase-tools は固定版でのみ実行する(未固定 npx はレジストリ乗っ取り時の
# 任意コード実行経路になる)。正の定義箇所・更新手順は下記
# 「firebase-tools のバージョン固定と更新手順」節を参照。
FIREBASE_TOOLS_VERSION="15.27.0" # 正の定義箇所。更新手順は下記節を参照
npx --yes "firebase-tools@${FIREBASE_TOOLS_VERSION}" emulators:start --only hosting --project demo-<name> || {
echo "エラー: firebase-tools@${FIREBASE_TOOLS_VERSION} の実行が失敗しました(該当版の不存在・レジストリ障害等、原因は問わない)。未固定 npx firebase-tools へのフォールバックは行わない。原因を確認してから再実行する。" >&2
exit 1
}
curl Items to check with:
B=http://127.0.0.1:5002
curl -s -o /dev/null -w '%{http_code}\n' "$B/about" # 200(リダイレクトなし)
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "$B/about/" # 301 → /about
curl -sI "$B/sw.js" | grep -i cache-control # no-cache
# .wasm は URL にグロブを使えないため、出力ディレクトリから実在する成果物のパスを特定して確認する
# 例: wasm_file="$(find <出力ディレクトリ> -name '*.wasm' -print -quit)" → そのパスを URL に変換して確認
curl -s -o /dev/null -w '%{content_type}\n' "$B/<実在する成果物のパス>.wasm" # application/wasm
curl -s -o /dev/null -w '%{http_code} redirects=%{num_redirects}\n' -L "$B/about/" # 無限リダイレクトがないこと
Step 5: Place the deploy workflow
.github/workflows/deploy.yml. Below is a Rust + wasm example.Replace the build section according to the target project.
Always match the deployment target branch to the default branch of the target repository (a required replacement item). In the examples and explanations of this skill, the default branch is written as main .gh repo view <owner>/<repo> --json defaultBranchRef -q .defaultBranchRef.name Check the actual default branch withmaster etc., and if it is not main , replace all occurrences of main / refs/heads/main(on.push.branches・deploy job の if- Deploy step's if) in the examples when generating the workflow. If you do not replace them, production deployment will never run even after merging.
Runners follow the organization's runner-policy (Fandhe-AI/actions の docs/runner-policy.md). For public repositories, use GitHub-hosted runners (ubuntu-latest etc.), and do not run untrusted code on self-hosted runners withpull_request . Even when using self-hosted runners for private repositories, be careful about trust boundaries withpull_request_target usage and jobs that handle secrets, and follow the procedures in runner-policy.md.
build job と deploy jobs are separated. pull_request Incargo run / cargo test) is executed, so this job is given neither a write-permission token nor Firebase secrets (persist-credentials: false で actions/checkout credentials are also not left in place).checks: write や FIREBASE_SERVICE_ACCOUNT Run only deploy job は push: main と workflow_dispatch . This is because if untrusted code and a write-permission token coexist in the same job and same event, a malicious PR could steal or abuse GITHUB_TOKEN or secrets during the build.
pull_request in build Run only the job, and do not perform preview deployment. If you pass FIREBASE_SERVICE_ACCOUNT or a write-permission GITHUB_TOKEN to a PR-triggered workflow, an attack surface for secret theft is created by malicious PRs (firebase.json such as modifying predeploy hooks). As a result,confirm the contents of the PR by the success of the build job, and confirm the actual delivery result in the production deployment after merge(this is an intentional constraint that prioritizes secure by default). If preview deployment becomes necessary, first consider a configuration that does not pass secrets or write permissions to PR-triggered jobs (a redesign of the trust boundary).
name: デプロイ
on:
push:
branches: [main] # 必須: 対象リポジトリの既定ブランチへ置き換える
pull_request:
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
env:
SITE_BASE_URL: https://${{ vars.FIREBASE_SITE_ID || '<site-id>' }}.web.app
jobs:
# 未信頼な PR コードをビルド・テストする job。write 権限のトークンも
# Firebase の secret も渡さない(persist-credentials: false で checkout の
# 資格情報も残さない)。
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: 前提の確認(fail-closed)
# 変数は run: へ ${{ }} で直接埋め込まず、env: 経由で環境変数として
# 渡す。式展開はシェルの解析前に行われるため、直接埋め込むと変数値に
# 仕込まれた文字列(例: `"; <command>; #`)が引用符を脱出して任意
# コマンド実行になる(インジェクション)。
env:
FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID }}
FIREBASE_SITE_ID: ${{ vars.FIREBASE_SITE_ID }}
run: |
test -n "${FIREBASE_PROJECT_ID}" \
|| { echo "FIREBASE_PROJECT_ID が未設定です。tools/bootstrap-firebase.sh を実行してください。"; exit 1; }
test -n "${FIREBASE_SITE_ID}" \
|| { echo "FIREBASE_SITE_ID が未設定です。"; exit 1; }
# GitHub ホステッド runner は毎回まっさらな環境のため ~/.cargo/bin が PATH に無い
- name: ツールチェーンを用意
run: |
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.cargo/bin:$PATH"
# 例: rustup target add wasm32-unknown-unknown / cargo install ...
- name: ビルド
run: cargo run --release # ← プロジェクトに合わせる
- name: テスト
run: cargo test --release # ← プロジェクトに合わせる
- name: 出力の検証
run: |
# ドメインは「リポジトリ変数」と突き合わせる。出力から推定した値と
# 比べる検証(sitemap 自身からベース URL を読む等)は、値が誤って
# いても必ず PASS するため意味がない
# -F で固定文字列として比較する(既定の正規表現解釈だと URL 中の
# `.` が任意文字に一致し、誤ったドメインでも検証を通過し得る)
grep -Fq -- "${SITE_BASE_URL}" dist/sitemap.xml \
|| { echo "sitemap.xml に ${SITE_BASE_URL} がありません(ビルドキャッシュが古い可能性)"; exit 1; }
! grep -Fq -- "example.com" dist/sitemap.xml \
|| { echo "プレースホルダのドメインが残っています"; exit 1; }
# 別ステップで生成する成果物(wasm 等)の欠落は静的チェックでは
# 検出できないことが多い。存在を明示的に確認する
# test -f dist/static/wasm/<name>_bg.wasm || { echo "wasm がありません"; exit 1; }
- name: 成果物をアップロード
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist
path: dist/
retention-days: 1
# secret と書き込み権限トークンを扱う job。未信頼コードは一切実行せず、
# build job が作った成果物を配信するだけに限定する。
# pull_request では実行しない(PR 起動の job には secret・write 権限を
# 渡さない)。workflow_dispatch を非 main ブランチから実行しても live へ
# デプロイしないよう ref ゲートも掛ける。
deploy:
needs: build
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
# action-hosting-deploy は結果を check-run として作成する。permissions を
# 明示すると未列挙のスコープは none になるため、checks を落とすと 403
# (Resource not accessible by integration)でデプロイ前に落ちる。
checks: write
steps:
# firebase.json(Step 3 で作成)を読み込むためリポジトリを checkout する。
# この job は main(信頼済み revision)でのみ実行される。
# checkout はデフォルトで作業ディレクトリをクリーンにするので、必ず
# download-artifact より前に実行する。
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: 成果物をダウンロード
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: dist
path: dist/
- name: 本番チャンネルへデプロイ(main)
# job 側の if と重複するが、防御多層として step 側にも ref ゲートを
# 明示する(job の条件が編集で緩められても live 直行を防ぐ)。
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
uses: FirebaseExtended/action-hosting-deploy@500ac625ca2dd40cbd15f7659af953801858032a # v0.11.0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: ${{ vars.FIREBASE_PROJECT_ID }}
channelId: live
Design points:
- Do not pass secrets or write permissions to PR-triggered workflows.
pull_requestJobs that run on theFIREBASE_SERVICE_ACCOUNTevent may execute untrusted code from the PR side, so do not give them secrets such asGITHUB_TOKEN. Limit the deploy job topush: mainとworkflow_dispatch. - live deploy is
github.ref == 'refs/heads/main'(the ref of the default branch). With only the condition of theworkflow_dispatchevent type, it would be possible to deploy to live whenmain, replace the ref as well). - If the deployment target is not configured, fail (do not skip). If you skip this, you cannot detect the state where "CI is green but the site is not updated"
- Cross-check domain verification against an independent source of information (repository variables). Verification that derives the expected value from the build output will always PASS even if the value is wrong. This was the only bug in this session with the property of "breaking silently"
- If it is a repository dedicated to implementation, no path filter is needed. If you place it in a monorepo,
paths:to narrow it down
Step 6: Confirm that it is actually deployed via CI
From local firebase deploy passing does not prove that the CI path passes. Create one PR, andbuild confirm that the job (build, test, output verification) turns green. Then merge it, anddeploy confirm that the job runs and the production channel is updated (because the configuration does not perform preview deploys on PRs, confirmation of the delivery result is done after merging).
# 固定版は Step 4 と同一値(FIREBASE_TOOLS_VERSION)。コードフェンスは独立シェルで
# 実行され得るため、このフェンス内でも代入を実行行に先行させる。
FIREBASE_TOOLS_VERSION="15.27.0" # 正の定義箇所は「firebase-tools のバージョン固定と更新手順」節
npx --yes "firebase-tools@${FIREBASE_TOOLS_VERSION}" hosting:channel:list --project <project-id> --site <site-id> || {
echo "エラー: firebase-tools@${FIREBASE_TOOLS_VERSION} の実行が失敗しました(該当版の不存在・レジストリ障害等、原因は問わない)。未固定 npx firebase-tools へのフォールバックは行わない。原因を確認してから再実行する。" >&2
exit 1
}
live is complete if the Last Release Time is updated to the CI execution time.
Pinning the firebase-tools version and the update procedure
Why: npx firebase-tools If you run it without pinning the version, npx will, if it is not in the local cache, immediately fetch and execute the latest version at that point in time from the registry without confirmation.firebase-tools(firebase/firebase-tools) package is taken over, this becomes a path for arbitrary code execution. Pinning to an exact version (X.Y.Z。dist-tag・^/~ ranges are prohibited) becomes the trust anchor.
How to decide the pinned version:
npm view firebase-tools versionto check the current latestnpm view firebase-tools repository.urlがfirebase/firebase-toolsto confirm that it isnpm view firebase-tools time --jsonetc. to confirm that the publication date and time are not unnatural
Update procedure:
- Update both the Step 4 fence and the Step 6 fence with the
FIREBASE_TOOLS_VERSIONをsame commit(the values must match exactly. Because the execution fence may be executed in an independent shell, the assignment is required in both fences) node --test skills/setup-firebase-hosting/tests/*.mjsto verify the exact semver, that all occurrences match, and that the execution line is pinned- Actually run it in one repository and confirm that the diff is normal
chore(setup-firebase-hosting): firebase-tools を X.Y.Z へ更新commit with
fail-closed: If the pinned version cannot be resolved (for any cause, such as the version not existing or a registry failure), npx exits with a non-zero status and stops. It does not retry by falling back to an unpinned npx firebase-tools .
Reference: bootstrap-firebase.sh(the script executed in Step 1) is designed to call Firebase addition and site creation directly against the REST API using the token of gcloud rather than the firebase CLI, and does not have an npx execution line (outside the scope of this section).
Common failures and causes
| Symptom | Cause |
|---|---|
addFirebase が 403 The caller does not have permission |
Firebase Terms of Service not accepted (same wording as insufficient IAM.testIamPermissions to isolate) |
403 SERVICE_DISABLED / requires a quota project |
In the API call, x-goog-user-project no header |
Deploy step is 403 Resource not accessible by integration |
permissions に checks: write is missing |
CI only command not found(passes locally) |
self-hosted runner's PATH has no ~/.cargo/bin is missing |
| CI is green but the site is not updated | The deploy step is being skipped (unset variables are not made to fail) |
| canonical and the actual URL differ by a 301 | trailingSlash settings conflict with the canonical format |
| Old JS/CSS/wasm continues to be served even after updating | For filenames without a content hash, immutable is being applied |
At deploy time, Unable to add channel domain to Firebase Auth |
Because the Auth role is not attached. Harmless if you do not use Auth |
Reasons for not choosing other GCP services
For services targeting Japan, all of them arenot a matter of cost but of geographic constraintsand are therefore unsuitable.
| Service | Reason for unsuitability |
|---|---|
| Cloud Storage static hosting | The free tier is limited to us-east1 / us-west1 / us-central1 . HTTPS requires Cloud Load Balancing (around $18/month) and does not become free |
| Cloud Run | The free egress transfer isonly 1 GB/month from North America. Delivery from the Tokyo region is billable from the very first request |
| App Engine Standard | Egress transfer is 1 GB/day, which is stricter than Firebase Hosting, and consumes instance time for static delivery |
Operations that cannot be automated
| Operation | Reason |
|---|---|
gcloud auth login |
Browser consent. Once per Google account |
| Acceptance of the Firebase Terms of Service | Console only(stated in the official documentation) |
| Registering DNS records for a custom domain | An operation on the registrar side. Cloud DNS is paid and the Spark guarantee is lost |
Related
- Post-build operations (weekly CI such as data freshness checks) should be prepared by each project
- Commits/PRs are
create-commit/create-prusing