---
title: Adapt the deploy workflow for other hosts
description: "Port the GitHub Pages recipe to Azure Static Web Apps, Cloudflare Pages, or Netlify by swapping four shared values and dropping in one host-specific config file."
canonical_url: https://usepennington.net/how-to/deployment/adapt-for-other-hosts/
sidecar_url: https://usepennington.net/how-to/deployment/adapt-for-other-hosts.md
content_hash: sha256:31d5fba75b0e76afa3ee1dabfb9af7a62f2a26d4713d7f3fd5e528cabda7d891
tokens: 2337
uid: how-to.deployment.adapt-for-other-hosts
reading_time_minutes: 5
---

Guides
# Adapt the deploy workflow for other hosts

Port the GitHub Pages recipe to Azure Static Web Apps, Cloudflare Pages, or Netlify by swapping four shared values and dropping in one host-specific config file.

 
With [Deploy to GitHub Pages](https://usepennington.net/how-to/deployment/github-pages.md) already in place and the `dotnet run -- build [baseUrl]` → `output/` → artifact pipeline understood, this page covers the deltas for Azure Static Web Apps, Cloudflare Pages, and Netlify. The material assumes the GitHub Pages recipe is the starting point.

 
## Before you begin

 
 - The canonical GitHub Pages workflow is committed and building cleanly.
 - A deploy target account exists (SWA resource, Cloudflare Pages project, or Netlify site) and the repo is connected.
 - The site serves either at the host's domain root (`baseUrl = "/"`) or under a known sub-path to pass as the first positional argument to `build`. See [Host under a sub-path (base URL)](https://usepennington.net/how-to/deployment/base-url.md) for the rewriter behavior.
 
 
For a working setup, see [examples/SubPathDeployableExample](https://github.com/usepennington/pennington/tree/main/examples/SubPathDeployableExample) — the `.github/workflows/deploy.yml`, `staticwebapp.config.json`, and `netlify.toml` siblings each express the same handful of settings in their host's syntax.

 
## Host deltas

 
Every host restates the same four settings — build command (`dotnet run --project <your-project> -- build "<base-url>"`), publish directory (`output/`, the default from `OutputOptions`), .NET SDK pin (`10.0.x`, matching `setup-dotnet@v4` in the GitHub Pages workflow), and base URL (`/` for apex domains, `/<path>` for sub-path hosting). The table below is the diff against the GitHub Pages workflow — it shows where each setting diverges per host. See [CLI and build arguments](https://usepennington.net/reference/host/cli.md) for the `OutputOptions.FromArgs` grammar.

 
    Concern GitHub Pages (canonical) Azure Static Web Apps Cloudflare Pages Netlify     Config file `.github/workflows/deploy.yml` `staticwebapp.config.json` + SWA's own build action Pages dashboard (no first-party config file) `netlify.toml`   Build command `dotnet run --project … -- build "$BASE_URL"` same (invoked via `Azure/static-web-apps-deploy@v1` `app_build_command`) same (set in dashboard → **Build command**) same (declared in `[build] command`)   Publish directory `output` (via `upload-pages-artifact@v3`) `output_location: "output"` on the SWA action **Build output directory:** `output` `publish = "output"`   .NET SDK pin `actions/setup-dotnet@v4` with `10.0.x` add `actions/setup-dotnet@v4` before the SWA action dashboard env: `DOTNET_VERSION = 10.0.x` `[build.environment] DOTNET_VERSION = "10.0.x"`   Base URL strategy derived from `${{ github.event.repository.name }}` `BASE_URL` env var, passed into `app_build_command` as `"${BASE_URL:-/}"`; apex by default `BASE_URL` env var, passed into the build command as `"${BASE_URL:-/}"`; apex by default `BASE_URL` env var with `/` default; override in dashboard per site   SPA / deep-link fallback `.nojekyll` marker + `404.html` `navigationFallback.rewrite: "/404.html"` (see Azure below) Cloudflare auto-serves `404.html` from build output `[[redirects]]` with `status = 404 → /404.html` (see Netlify below)   Cache headers for `/_content/*` GitHub Pages default (short TTL) `routes[]` entry, `Cache-Control: public, max-age=31536000, immutable` `_headers` file in `output/` (same directive) `[[headers]] for = "/_content/*"` (same directive)   `.nojekyll` needed? yes no no no    
## Azure Static Web Apps

 
Commit `staticwebapp.config.json` at the repo root; SWA reads it during deploy and applies routes, MIME overrides, nav fallback, and 404 handling. In the SWA workflow (`.github/workflows/azure-static-web-apps-<id>.yml`, generated by the Azure portal), set `app_build_command` to `dotnet run --project <your-project> -- build "${BASE_URL:-/}"` and `output_location` to `output`. The `${BASE_URL:-/}` expansion is what carries the sub-path through — a bare `-- build` always deploys at the apex regardless of the env var. Define `BASE_URL` as a workflow `env:` entry (or leave it unset for apex hosting). Everything else from the GitHub Pages workflow applies unchanged.

 
```json:symbol
{
  "$schema": "https://json.schemastore.org/staticwebapp.config.json",
  "trailingSlash": "auto",
  "mimeTypes": {
    ".json": "application/json",
    ".xml": "application/xml",
    ".webmanifest": "application/manifest+json"
  },
  "routes": [
    {
      "route": "/sitemap.xml",
      "headers": {
        "Cache-Control": "public, max-age=3600"
      }
    },
    {
      "route": "/llms.txt",
      "headers": {
        "Cache-Control": "public, max-age=3600"
      }
    },
    {
      "route": "/_content/*",
      "headers": {
        "Cache-Control": "public, max-age=31536000, immutable"
      }
    }
  ],
  "navigationFallback": {
    "rewrite": "/404.html",
    "exclude": [
      "/_content/*",
      "/*.{css,js,json,png,jpg,jpeg,gif,svg,webp,ico,woff,woff2,ttf,xml,txt,webmanifest}"
    ]
  },
  "responseOverrides": {
    "404": {
      "rewrite": "/404.html"
    }
  },
  "globalHeaders": {
    "X-Content-Type-Options": "nosniff",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  }
}
```

 
## Netlify

 
Commit `netlify.toml` at the repo root; Netlify autodetects it and no dashboard build-setting changes are needed beyond linking the repo. `BASE_URL` defaults to `/` — override it in **Site configuration → Environment variables** for sub-path hosting. The `[[redirects]]` block with `status = 404` routes deep-link misses to the generated `output/404.html`.

 
```toml:symbol
# Netlify configuration for a Pennington static site.
#
# Netlify serves `publish = "output"` verbatim. Set `BASE_URL` in the
# Netlify dashboard (Site configuration → Environment variables) if you
# need a sub-path; for a root-served site leave it as the default `/`.
#
# The 404 fallback uses Netlify's conditional `status = 404` rewrite so
# deep-link misses return the generated `output/404.html` page body
# instead of Netlify's default 404 shell.
  
[build]
  command = "dotnet run --project examples/SubPathDeployableExample -- build ${BASE_URL:-/}"
  publish = "output"
  
[build.environment]
  DOTNET_VERSION = "10.0.x"
  BASE_URL = "/"
  
[[headers]]
  for = "/_content/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
  
[[headers]]
  for = "/*"
  [headers.values]
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"
  
# Pretty-URL fallback: Pennington's DocSite emits `<slug>/index.html`,
# which Netlify already serves at `/<slug>/`. The explicit 404 rule
# below only fires when nothing else matches.
[[redirects]]
  from = "/*"
  to = "/404.html"
  status = 404
```

 
## Cloudflare Pages

 
Cloudflare Pages has no first-party config file equivalent to SWA or Netlify, so the four shared values live in the project dashboard under **Settings → Builds & deployments**:

 
 - **Build command:** `dotnet run --project <your-project> -- build "${BASE_URL:-/}"` — the `${BASE_URL:-/}` expansion passes the env var through as the base-url argument, defaulting to `/` when it is unset. A bare `-- build` would ignore `BASE_URL` entirely and always deploy at the apex.
 - **Build output directory:** `output`
 - **Environment variables:** `DOTNET_VERSION=10.0.x`, plus `BASE_URL=/<path>` when serving under a sub-path (leave `BASE_URL` unset for apex hosting).
 
 
For custom cache headers on `/_content/*`, drop a `_headers` file into `wwwroot/` so it ships as part of `output/` — the directive format matches the Netlify and Azure snippets above.

 
## Verify

 
 - Trigger a deploy on the target host. The build log shows `setup-dotnet` (or equivalent) picking up `10.0.x`, `dotnet run -- build` exiting zero, and the host uploading `output/` as the publish directory.
 - Open the deployed URL — the landing page loads, nested links resolve, and view-source shows the expected `<body data-base-url="...">` (either absent for root deployments, or `/<path>` with no trailing slash for sub-path hosts).
 - Visit a non-existent path like `/does-not-exist/` — the response body is the generated `output/404.html` rather than the host's default 404 shell.
 
 
## Related

 
 - Recipe: [Deploy to GitHub Pages](https://usepennington.net/how-to/deployment/github-pages.md) — the canonical workflow this page diffs against.
 - Recipe: [Self-host behind Nginx or IIS](https://usepennington.net/how-to/deployment/self-host.md) — for hosts where you own the web server config instead of a managed platform.
 - Recipe: [Host under a sub-path (base URL)](https://usepennington.net/how-to/deployment/base-url.md) — how `BaseUrlHtmlRewriter` prefixes internal URLs when the host serves under `/<path>/`.
 - Reference: [CLI and build arguments](https://usepennington.net/reference/host/cli.md) — the `build [baseUrl] [outputDirectory]` surface every host command above invokes.
 
 
[Previous
                
                Deploy to GitHub Pages](https://usepennington.net/how-to/deployment/github-pages.md)[Next
                    
                Self-host behind Nginx or IIS](https://usepennington.net/how-to/deployment/self-host.md)