Deploy
One command deploys Arc CMS to a Firebase project: it asks which project and what to deploy, builds what is needed, and checks the result. Scripts and CI pass the same choices as flags.
Deploying a new project for the first time is covered in Deploy for the first time. To send just one function or just the rules, see Deploy only what changed.
Deploy with the guided deploy
Run it in a terminal and answer the questions. Each has a default in brackets; press Enter to take it.
npm run deploy
-
Which Firebase project? The aliases in
.firebaserc, plus the project chosen withfirebase use. The default is the project you deployed to last from this checkout. "Another project id" takes any other. -
Its settings. The deploy prints the database, website, storage bucket, app users and functions region the project uses, and, for a project with its own web settings, whether its website builds like production or in development mode (see Build like production). They come from
arccms.config.json, and the files generated from it are rebuilt on every deploy, so they are never stale. A project with no settings yet is offered a short setup, the same asnpm run arc:configure(see Configure a project). -
What to deploy? See the table below. A choice whose files changed since its last deploy is marked "(changed since the last deploy)".
-
Confirm. The summary shows the project, the Firebase config file and the exact command. For a production project (one marked
--production=yes, the aliasproduction, or an id withprod,productionorliveas a part of it) you type the project id instead of pressing Enter.
The choices, remembered per project in .arc-deploy-state.json (not committed), are the defaults next time.
What each choice deploys
| Choice | Firebase targets | Notes |
|---|---|---|
| Only the functions changed since the last deploy | One functions:arccms:arccms.<name> per function | Offered after a functions deploy from this checkout. How it is worked out: Deploy only what changed |
| Everything | functions:arccms, firestore, storage, hosting | Storage and the website only where they apply (below). With the website, it builds the site first and publishes every page again after |
| All functions | functions:arccms | Deletes functions the build no longer has, after asking (see Functions that are deleted) |
| Database rules and indexes | firestore | The rules and indexes are combined with the app's own first (see Security rules) |
| Storage rules | storage | Not offered when Arc CMS shares the default bucket with another app |
| Website | hosting | Builds the site, deploys it keeping the published pages, then publishes every page again |
The website is offered for every project that has one (not for a project set up with --site=none). A deploy always builds what it deploys: the website is built for the project being deployed to, with that project's Firebase web settings, so a site can never go out talking to another project. Which settings that is, and how to add a project such as staging, is on Dev, staging and production.
Deploy from a script or CI
With any option, npm run deploy skips the guided questions and passes the options on to firebase deploy. The questions it can still ask, in a terminal, are before it deletes functions (see Functions that are deleted), the id of a project marked production, and, once per project, whether to set up phone sign-in's Google Cloud setting. Off a terminal it asks nothing: what would need an answer takes a flag instead. For a whole CI job, see Deploy from CI. Off a terminal and with no options, it prints these examples and stops.
npm run deploy -- --only functions:arccms --project default
npm run deploy -- --only functions:arccms:arccms.<name> --project default
npm run deploy -- --only firestore --project default
npm run deploy -- --only storage --project default
npm run deploy -- --only hosting --project default
Copy these as they are, except for the project. arccms in functions:arccms is not a project or database name: it is the name of the Arc CMS functions codebase in firebase.json, the same in every install, and the arccms- in front of every function name. The part that differs from person to person is --project: an alias from your own .firebaserc (default, production) or a project id.
| Option | What it does |
|---|---|
--project <alias or id> | The project. Without it: the firebase use project, else the default alias. The project is always passed to the Firebase CLI |
--only <targets> | What to deploy, as the Firebase CLI takes it. No --only deploys everything in the config |
--yes | Lets a deploy of all the functions delete the ones the build no longer has without asking (needed off a terminal). Not passed on to Firebase itself; when there is something to delete, the deploy adds --force for it |
--force | The same as --yes, and passed on to Firebase |
--confirm-production=<project id> | Needed to deploy a project marked production (arc:configure --production=yes): the id of the project this deploys to. Without it, a terminal asks for the id and anywhere else nothing is deployed. An id that is not the target's stops any deploy. Not passed on to Firebase |
--probe | After a deploy that included functions, checks that every callable can be reached from the browser (see Check the callables) |
--non-interactive | A Firebase CLI option, passed on: the CLI asks nothing |
--config <file> | A Firebase config of your own. Otherwise the project's generated firebase.<projectId>.json is used when npm run arc:configure wrote one, else firebase.json |
Either way, the deploy also:
- builds the functions (
npm run build --prefix functions) before any deploy that includes them; - waits 65 seconds and deploys again, up to two times, any function the Firebase CLI started but never reported as done (Google limits how many functions can change per minute). When the error says Google is still setting up Eventarc, as on a project's first deploy, it waits 5 minutes instead (see A first deploy to a new project);
- when a deploy that includes the website fails before the website step, does not deploy the website, says so and prints the commands that deploy it alone and publish its pages;
- fails when a function still never reported success, and prints the command that redeploys it;
- stops with a plain message when you ask for hosting on a project with no website, or for storage rules on a bucket shared with another app;
- records a whole deploy of a part (all functions,
firestore,storage,hosting) for the guided deploy's "only what changed"; - with the
smsfeature on, checks once per project, withgcloud, that phone sign-in can sign people in (the IAM Service Account Credentials API and the Token Creator role for the functions' service account), and in a terminal offers to set up what is missing. A yes or a no is remembered in.arc-deploy-state.json; withoutgcloudit says so once. It never stops the deploy. See Google Cloud setup for phone sign-in.
Plain firebase deploy skips all of this. It ships the functions exactly as they were last built, so new functions are silently missing, and it uses firebase.json, so a project set up with its own database, bucket or website gets the wrong ones. Always deploy through npm run deploy.
deploy:dev and deploy:prod
Two scripts deploy everything to the default or production alias in one go:
npm run deploy:dev
npm run deploy:prod
Each runs the deploy with --project <alias> --non-interactive --force and no --only, which builds the website for that project and the functions first, then publishes the static pages (npm run seed:dev or npm run seed:prod). For any other project, use npm run deploy -- --project <alias>, then npm run seed -- <alias>.
Both pass --force, so they delete the deployed functions the build no longer has without asking. Use the guided deploy when you are not sure what the build contains.
When the production project is marked production, deploy:prod asks for its id in a terminal, and anywhere else stops before it deploys. From a script, run the deploy with the id instead, then the seed: npm run deploy -- --project production --confirm-production=<project id> --non-interactive --force and npm run seed:prod.
Pages left open across a deploy
A website deploy replaces the hashed code files on Hosting. A visitor whose page was open before it keeps the old version until the page loads again. When they open a screen the old page had not loaded yet, its code file is gone: Arc CMS then loads that address afresh, so they land on it in the new version. An installed app offers its update instead (see Open screens offline and after a deploy).
Regions
Every database read or write a function makes travels to the database and back. Functions far from their database are slow: with the database in Mumbai (asia-south1) and the functions in Iowa (us-central1, the Firebase default), one sign-in step takes several seconds. So the functions run next to the database, in the region set by functionsRegion (see Config keys).
Before every deploy that includes functions, the deploy compares the two. It looks up the database's location once per project and remembers it in .arc-deploy-state.json.
- No Arc CMS function deployed yet: the deploy sets
functionsRegionto the database's region, says so, and deploys there. A website in the same deploy is built after this, so it calls the functions in their new region. The region is saved inarccms.config.json, which git ignores, and insrc/environments/arc-install.ts: commit that one, the website reads it. Wherearccms.config.jsonis recreated, as in CI, pass--functions-regiontoarc:configurefrom then on (see Deploy from CI). - Functions already deployed somewhere else: the deploy warns and deploys anyway. It never moves live functions. The guided deploy asks once whether to keep warning for that project.
A database in a multi-region location counts as next to the functions in one of its regions: nam5 with us-central1, eur3 with europe-west1. A location with no functions of its own gets the nearest region that has them.
The region reaches three places, all written by npm run arc:configure: the functions (ARC_FUNCTIONS_REGION), the website, which calls them there (arc-install.ts), and the Hosting rewrites to the unsubscribe and preference functions (firebase.<projectId>.json). To choose it yourself, before the first deploy:
npm run arc:configure -- --project=default --functions-region=asia-south1
Changing functionsRegion after the functions are deployed moves the whole install: a deploy creates every function in the new region, the website must be built and deployed again to call them there, and the old ones stay until a full functions deploy deletes them. Plan it as a release, not a setting.
A region costs a little more or less than another per call, but at the free allowance most sites stay inside, the difference is nothing, and functions next to the database also stop the paid traffic between regions.
A first deploy to a new project
The first deploy of functions to a project turns on the Google Cloud services they need, without asking: Cloud Functions, Cloud Build, Artifact Registry, Cloud Run, Eventarc, Pub/Sub, Cloud Storage, Cloud Scheduler (for the scheduled functions) and, for a deploy of everything, Firebase Extensions. The project must be on the Blaze plan. Turning a service on costs nothing; you pay for what the functions use, and Artifact Registry stores each function's built image. The account that deploys needs permission to turn services on (Owner, Editor or Service Usage Admin).
Two failures are normal on that first deploy, and the deploy's own retry clears them:
- The Firestore triggers fail with "Permission denied while using the Eventarc Service Agent" and "Retry the deployment in a few minutes": Google is still giving Eventarc, which delivers the database events, its permissions. The deploy waits 5 minutes and deploys just those, twice at most. If they still fail, it says so: wait a few minutes and run the same deploy again.
onSignInDeleted, the one first generation function, can fail with "Build failed: Build error details not available". Its build looked for the project's image repository (gcf-artifacts) a few seconds before Cloud Functions created it for the other functions. The retry a minute later builds it.
Neither needs a setting changed. When the functions fail, the website is not deployed in the same run; the deploy says so and prints how to deploy it alone.
Functions that are deleted
A deploy of all the functions (--only functions, --only functions:arccms, or no --only) makes the project match the build, so it deletes the Arc CMS functions the build no longer has: those of a feature turned off in src/custom/features.ts, a search collection removed from functions/src/custom/search-sources.ts, or a function that was renamed.
- Before deploying, it lists them and asks once. The guided deploy asks on its own prompt.
- Off a terminal it needs
--yes(or--force). Without it, it stops and deploys nothing. - If the Firebase CLI skips a delete (it does when another function fails), the deploy deletes it afterwards.
- A deploy of named functions (
--only functions:arccms:arccms.<name>) never deletes anything. - Only the
arccmscodebase is compared, so another app's functions in the same project are never touched.
npm run deploy -- --only functions:arccms --project default --yes
A deleted function's URL stops working, for example a payment webhook registered with the payment provider or a link in an email already sent.
When the order matters
"Everything", deploy:dev and deploy:prod run one firebase deploy, and the Firebase CLI decides the order of the parts. That is fine for most changes. When a change tightens a security rule so that the browser must go through a new function, or stops the browser reading something older website code still reads, deploy in steps instead, so nothing is refused in between:
The functions, so the new ones exist.
The indexes (
npm run deploy -- --only firestore:indexes --project <alias>). Indexes build in the background: wait until the Firebase console shows them enabled.The website, so no page still does what the new rules refuse.
The rules, last (
npm run deploy -- --only firestore:rules --project <alias>, andstorageif they changed).
If the rules go out first, the refused reads and writes fail at once. To recover, deploy the previous rules again from git.
After the deploy
Check the callables
Firebase lets the browser call a callable function when it creates it. If that step is missed, for example in a partly failed deploy, every browser call to it fails with HTTP 403, and later deploys do not repair it. The guided deploy checks every callable it just created. With flags, add --probe, or run the check yourself after a deploy that adds a callable and before a release:
bash functions/scripts/check-callable-access.sh
It checks the project a deploy would go to (the firebase use project, else the default alias) in that project's functions region, and says which as it starts. Set FIREBASE_PROJECT=<alias or id> or FIREBASE_REGION to check another. It reads the callables from the functions build (plus the app's list in functions/src/custom/public-callables.txt), so build the functions first; GCLOUD_PROJECT is not needed. Give function names after the script to check only those, without the arccms- prefix (custom-<name> for an app's own).
| Result | Meaning | Fix |
|---|---|---|
| reachable | The browser can call it | Nothing |
| BLOCKED by Cloud Run | The function exists but the public may not call it | Run the gcloud run services add-iam-policy-binding command the script prints, or delete the function and deploy it again (a fresh create grants access) |
| NOT DEPLOYED (404) | The build has it, the project does not | Rebuild the functions and deploy again |
| Nothing was checked | No project, no functions build, or a build it could not read; the line above says which. Exit code 2, and the deploy says the callables were not checked, never that they are blocked | Fix what it names, then run the check again |
Publish the pages again
Published pages are static files that Cloud Functions write to Hosting; none of them is in the build. Every website deploy through npm run deploy keeps them: it uploads the build to a preview channel named arc-deploy first, then releases one live version holding the build and every published page, so visitors and search engines never see a page drop out. With the seo feature off, it leaves out robots.txt, the sitemap, llms.txt, llms-full.txt, the IndexNow key file and the feeds, so one website deploy removes the ones an earlier publish wrote. Every other part (functions, rules, indexes) goes through firebase deploy as before.
Kept pages still show the old templates, header and footer, so after a website deploy every page is published again from the new files. Who does that:
| What you run or do | What is published again |
|---|---|
The guided npm run deploy, choosing Website or Everything | Every page, by itself, once the deploy succeeds |
npm run deploy:dev, npm run deploy:prod | Every page, by itself |
npm run deploy -- --only hosting --project <alias> (it builds the site first) | Nothing. The published pages are kept, but run npm run seed -- <alias> afterwards |
| Publishing or unpublishing a content item | That item's pages in every language, its type's list page, the home page when it shows that type, the sitemap and the feeds |
| Republish website (Content, Content types) | Every content page and list page, the home page, the static pages, the sitemap and the feeds, in one release |
| Saving Settings, About, the site address, the languages or the powered-by setting | The home page |
To publish every page again from a terminal:
npm run seed:dev
npm run seed:prod
npm run seed -- staging
seed:dev publishes to the default alias, seed:prod always to production, and npm run seed -- <alias or id> to any project. It republishes the home page in every language, every published content page and list page, every static page the site has, robots.txt, llms.txt, the sitemap and the RSS feeds. It runs on your machine with your Firebase CLI login (run firebase login first), not through a Cloud Function, and does nothing for a project with no website. The seedStaticPages callable does the same from the browser, for admins only.
Publishing reads the templates, header, footer, strings and static pages from the live website (/_site/), so the website deploy comes first and the republish after it, as the guided deploy does. Publishing or Republish website before the deploy still uses the old files. A change to the publish functions that reads site files at a new place needs the functions and the website deployed together, then the republish.
Deploy the website with npm run deploy, never with firebase deploy --only hosting. The plain command releases only the build, so every published page drops out until the republish, and it ignores this install's own config: on a project with two installs it goes to the wrong site.
When the deploy stops
| Message | What to do |
|---|---|
| "... is set up with the database ... but this checkout has no settings for it" | The project was set up on another computer. Copy its arccms.config.json here, then deploy again |
| "arccms.config.json says the database is ..., but src/environments/arc-install.ts says ..." | The two were changed separately. Run npm run arc:configure -- --project=<alias>, check the result and commit src/environments/arc-install.ts |
| "Hosting is off for ..." | The project publishes no website. Deploy the other parts |
| "Storage rules are not deployed for ..." | Arc CMS shares the default bucket with another app, and a bucket has one rules file. Give Arc CMS a bucket of its own (see Sharing a Firebase project) |
| "Not deleting without a yes" | A deploy of all the functions would delete some. Add --yes, or deploy only the functions you changed |
| "The functions build failed, so nothing was deployed" | Run npm run build --prefix functions to see the TypeScript errors |
| "These functions were started but never reported success" | The old version may still be live. Run the redeploy command it prints |
| "Google is still setting up Eventarc for this project's first triggers" | A project's first deploy (see A first deploy to a new project). Wait a few minutes, then run the same deploy again |
| "The website was not deployed: the deploy failed before it" | Fix what failed above it, then run the two commands it prints: the website alone, then its pages |
More problems and their fixes are in Troubleshooting.