Skip to content

Deploy a Canvas Headless app

Goal: take a Canvas Headless app, in any of its four frameworks, live on Front End Hosting – Advanced.

  • Your app live on a Front End Hosting – Advanced environment, serving on the platform’s port.
  • A prebuilt artifact your CI can reproduce: built output, node_modules, and one config file.
  • The one purge command that makes your first deploy actually visible.
  1. Front End Hosting – Advanced does not build your app. It checks out the git ref you deploy, and runs npm start in it. Everything else follows from that:

    • The artifact you push is the runnable app: built output, package.json with a start script, and a committed node_modules directory. An artifact without node_modules fails to deploy, even when your framework’s build output bundles its dependencies.

    • The platform injects PORT=3000 and routes traffic to it. All four frameworks read it natively.

    • The readiness probe connects over IPv6, so the server must listen on all interfaces including IPv6 (the :: bind). Next.js, Nuxt, and TanStack Start do this by default; Astro needs one config line (step 2). A server bound to localhost or an IPv4-only address runs fine while every deploy fails.

    • Keep the start script a plain command (node server.js, next start). Deploys where the script carried an environment-variable prefix (HOST=:: node ...) failed with the app producing no output; put configuration in the build or in environment variables instead.

    • One file at the artifact root tells the platform to skip its own build step:

      acquia_config.yaml
      enable-prebuilt-artifact: true
  2. Build the artifact from your scaffolded app

    Section titled “Build the artifact from your scaffolded app”

    The Canvas Headless templates build without a reachable Canvas site (nothing is fetched or baked in at build time), so CI can build the artifact anywhere. Run the build on Linux amd64 so native packages in node_modules match the platform. Each tab lists what the scaffolded template already has, and the pieces to add.

    The template’s next start already runs the built server and reads the platform’s port. To make the checkout deployable:

    1. Remove /.next/ and /node_modules/ from .gitignore, build, and commit both (.next/cache/ can stay ignored).
    2. Add acquia_config.yaml from step 1.
    {
    "scripts": {
    "build": "next build",
    "start": "next start"
    }
    }

    The committed node_modules is what next start runs from; sharp is its only platform-specific package, matched automatically when the build runs on Linux amd64. To ship a far smaller artifact, Next’s standalone mode also works on this platform (output: 'standalone' through the template’s withCanvas(), start: node server.js, copy public/ and .next/static/ into the standalone directory after building).

  3. Set the runtime variables before the first deploy

    Section titled “Set the runtime variables before the first deploy”

    On the environment page, add CANVAS_SITE_URL pointing at your live Canvas site. Variable names on Front End Hosting may use letters, numbers, and underscores, and must not start with a number, AH_, or ACQUIA_; the CANVAS_* names pass. The plain-Node servers do not read .env files, so the environment variable is the only control point in production.

    Order matters: the app fetches content on every request, so with no reachable Canvas site the homepage renders an error, the readiness probe fails, and the deploy itself fails. Set the variable first, then deploy. Make sure the site’s front page resolves to a published Canvas page, so / renders 200 for the probe.

  4. Push the artifact to your application’s repository

    Section titled “Push the artifact to your application’s repository”

    Your application’s git URL is on its overview page in the Cloud Platform user interface. Push the artifact as its own branch:

    Terminal window
    git checkout -b deploy-artifact
    git add -A -f && git commit -m "Build artifact"
    git push origin deploy-artifact

    If the push is refused, the two causes worth checking first: your key is not RSA, or your team role lacks repository access. Both produce the same Permission denied (publickey).

  5. Terminal window
    acli api:environments:code-switch <environment-id> "deploy-artifact" --task-wait

    The same action is Switch code on the environment page in the Cloud Platform user interface. The task takes 5 to 15 minutes. If it fails, the previous release keeps serving; nothing goes dark.

  6. Purge the cache, or keep seeing the old site

    Section titled “Purge the cache, or keep seeing the old site”

    The platform fronts your environment with a cache, and a deploy does not purge it. On a new environment, the platform’s placeholder page carries a one-year cache lifetime, so your homepage keeps showing it even after a green deploy:

    Terminal window
    acli api:environments:domain-clear-caches <environment-id> <your-environment-domain>

    Purge after every deploy. Cached responses from the previous release persist the same way, error pages included.

  7. Terminal window
    curl -s https://<your-environment-domain>/ | grep -o "<h1[^>]*>[^<]*</h1>"

    Your homepage’s heading in that output means the app is live and serving from the platform’s port.

The platform checked out your branch, found enable-prebuilt-artifact: true, skipped its build system, and ran npm start with PORT=3000 injected. Your framework’s production server bound all interfaces, read the port from the environment, and answered the platform’s readiness probe on /. The purge step made the new release visible through the cache. Every later deploy is the same loop: build in CI, push the branch, switch, purge.

The homepage still shows “Welcome to Acquia Cloud”

that is the platform’s placeholder, cached with a one-year lifetime, not a failed deploy. It appears on every new environment (its copy talks about Drupal; on this product, ignore it). Run the purge from step 6.

Code switched task fails while the site keeps working

the previous release keeps serving through a failed deploy, and the environment’s deployed ref still moves to the new branch. In order of likelihood, verified causes: the server binds localhost or IPv4 only (Astro without server: { host: '::' }); the artifact has no committed node_modules; the start script carries an environment-variable prefix or does not run the built server; acquia_config.yaml is missing from the artifact root; the app’s content backend is unreachable so / errors and the probe fails.

Changed a variable and the app has not picked it up

variable changes apply through a platform task, and the app restarts with the new values about a minute after the task completes. If it never appears, check the name against the naming rule in step 3.

Permission denied (publickey) on push

the platform accepts RSA keys only, and a key on your profile is not enough; your team role must include repository access.

Provisioning is automatic: signing the contract creates the Front End Hosting application with its environments (a verified new application came with Prod and Staging, Node.js 22 preselected). If your application is missing, file a support ticket.

Was this page helpful?