# Hosting

TheYard is on the internet at https://theyard.stevenstout.biz. This page is the
parent record for how that works, written for a reviewer or anyone
learning how a small production setup fits together. The records under it in
this menu are its children: read this page for the shape, open a child for the
full reasoning behind one decision. Everything, the infrastructure code
included, is served from these menus; nothing requires opening the repository.

## In plain words

This page explains how the site reaches the internet: a domain name and a free edge service (Netlify) that handles the secure connection, in front of three web apps on one Azure machine (an App Service plan): one per site, and a rendering service that draws every page before it leaves. The piece still waiting is a production edge (Azure Front Door) that the free trial refuses to create.

In practice, a developer sees how a small production setup fits together, with its infrastructure code served on the site, and the organization runs it on free services and one small plan while the full design waits behind one parameter.

## The picture

[![TheYard infrastructure: a request from the browser through Wix DNS and the Netlify edge to the rendering service and the site's web app on the App Service plan on Azure; a green gate's push through the three deploys to the registry and the roll; and Azure Front Door, designed, parameterized and still refused](/api/docs/images/infrastructure.svg#1400x1480)](https://theyard.stevenstout.biz/api/docs/diagrams/infrastructure)

*A preview. [Open the infrastructure diagram in a new page](https://theyard.stevenstout.biz/api/docs/diagrams/infrastructure)
to zoom in and follow it; every diagram on this site opens that way (ADR: Diagram pages).*

Three lanes: a request from left to right, through the rendering service
and the API it reads, a merge becoming a roll, and the one piece of the
production design that still waits for a subscription upgrade. Every name in it is
the one the records and the pipeline logs carry. The source is
[`docs/images/infrastructure.svg`](https://github.com/SteveStout/TheYard/blob/main/docs/images/infrastructure.svg); the records below explain each box.

Since 1.0.0.100 there are two sites behind that edge, and the second
drawing is how the two names reach the two web apps on one plan, how a
page reaches the rendering service beside them, and how both sites reach
both stores (ADR: A permanent address for the second site; ADR: One plan,
two sites):

[![TheYard's two sites: two names at Wix, one Netlify edge, two web apps and the rendering service on one App Service plan on Azure, both stores behind both](/api/docs/images/two-sites.svg#1520x1111)](https://theyard.stevenstout.biz/api/docs/diagrams/two-sites)

*A preview. [Open the two-sites diagram in a new page](https://theyard.stevenstout.biz/api/docs/diagrams/two-sites)
to zoom in and follow it.*

## Websites and resources used

- **Azure (portal.azure.com).** Runs the app: one Linux B2 App Service plan
  for the compute, carrying three web apps for containers: one per site since
  1.0.0.156 (ADR: One plan, two sites), and since 1.0.3.103 the rendering
  service that draws every page for both (ADR: A rendering service beside the
  API); Container Registry for the two images;
  Azure SQL Database for the store the first site is on and Azure Cosmos DB
  for the second's, with both stores opened by both sites since 1.0.0.94
  (ADR: One container, both stores). The only place code executes. For its
  first three weeks the compute was Azure Container Instances, one container
  group per site.
- **Wix (wix.com).** The domain registrar. Holds stevenstout.biz and answers
  DNS; two CNAME records, theyard and theyard-cosmos, point the two sites at
  the same edge.
- **Netlify (netlify.com).** The free edge. Terminates HTTPS for both names
  on one certificate, and forwards every request to Azure unchanged, over
  HTTPS: theyard to the first web app, theyard-cosmos to the second (ADR: A
  permanent address for the second site).
- **Let's Encrypt (letsencrypt.org).** Issues the certificate at no cost;
  Netlify renews it automatically.
- **Cloudflare (cloudflare.com).** Configured and dormant; becomes the edge
  after the domain transfers registrars, around late October 2026.
- **GitHub (github.com/SteveStout/TheYard).** Holds the code, runs the deploys
  when the ship's gate pushes, runs the test wall on a pull request, and feeds the edge
  deploys.

## The chain, request by request

1. **DNS.** theyard.stevenstout.biz and theyard-cosmos.stevenstout.biz are
   two CNAME records at the registrar (Wix), both pointing at the same edge.
   TTLs sit at 30 minutes while the setup is young so changes propagate fast.
   They get lengthened once things are boring.
2. **Edge.** Netlify's free tier terminates HTTPS and forwards every request
   unchanged. The entire edge is three files in this repository, deployed from
   GitHub on every push that touches them. The name a request arrived on
   picks the site, and the path picks the origin: a page, the bare path with
   its query, goes to the rendering service under that site's name, and
   everything else, the bundle, the photographs and `/api`, goes to that
   site's API (ADR: A permanent address for the second site; ADR: A rendering
   service beside the API).
3. **Origin.** One App Service plan, `PLAN-THEYARD-SS`, Linux B2 in West US 3,
   runs the API's Docker image twice in RG-THEYARD-SS, two web apps for
   containers, each answering HTTPS on its own `azurewebsites.net` name and
   listening on port 8080 inside, and the rendering service's image once, a
   third web app that draws a page for either site by reading that site's
   API. Azure does all the compute. The edge only forwards. Both
   stores are opened by both sites since 1.0.0.94, and the second site runs
   the same image with Azure Cosmos DB as its default; since 1.0.0.100 it
   answers at https://theyard-cosmos.stevenstout.biz through the same edge
   and the same certificate. Each site is one store's site, and the Store bar
   at the top of every page links to the other at the same page (ADR: One
   container, both stores). Why one plan and why West US 3 are ADR: One plan,
   two sites, which also records the move from B1 to B2 when the rendering
   service arrived.

The edge's rules, read from this build. The first matching line wins, top to
bottom, so each domain's page rule sits above its catch-all:

```text
https://stevenstout.biz/* https://theyard.stevenstout.biz/:splat 301!
https://www.stevenstout.biz/* https://theyard.stevenstout.biz/:splat 301!
http://stevenstout.biz/* https://theyard.stevenstout.biz/:splat 301!
http://www.stevenstout.biz/* https://theyard.stevenstout.biz/:splat 301!
# The second site, whose default store is Azure Cosmos DB, under its own name
# (ADR: A permanent address for the second site). These two sit above the
# catch-all because the first matching rule wins, top to bottom, and the
# catch-all matches everything.
#
# Both origins are web apps on one App Service plan since 1.0.0.156, reached
# over HTTPS (ADR: One plan, two sites). Until the stopped container groups are
# deleted, the way back is `az container start` on both and these two origins
# in place of the two below:
#   http://theyard-cosmos-ss-zmnetj67bn5h2.westus2.azurecontainer.io:8080
#   http://theyard-ss-zmnetj67bn5h2.westus2.azurecontainer.io:8080
# Every page is the bare path with a query naming the view, the vehicle or the
# document, and the rendering service draws it (ADR: A rendering service beside the API); each
# domain's page goes to the service under that site's name, and everything else,
# the bundle, the photographs and /api, to that site's API as before. The query
# goes with a 200 rewrite. /index.html is still the API's own page, drawn in the
# browser, which is the way round while the service is down.
https://theyard-cosmos.stevenstout.biz/ https://app-theyard-render-ss-zmnetj67bn5h2.azurewebsites.net/site/cosmos 200!
https://theyard-cosmos.stevenstout.biz/* https://app-theyard-cosmos-ss-zmnetj67bn5h2.azurewebsites.net/:splat 200!
http://theyard-cosmos.stevenstout.biz/* https://theyard-cosmos.stevenstout.biz/:splat 301!
/ https://app-theyard-render-ss-zmnetj67bn5h2.azurewebsites.net/site/sql 200!
/* https://app-theyard-ss-zmnetj67bn5h2.azurewebsites.net/:splat 200!
```
*Live from [`edge/_redirects`](https://github.com/SteveStout/TheYard/blob/1d6b50b/edge/_redirects#L2-L27), region `rules`, read from this build at 1d6b50b.*

## The certificate

Let's Encrypt at the edge, issued and renewed automatically, one certificate
for all four names (the bare domain, www, theyard and theyard-cosmos).
Nothing was purchased and nothing expires by surprise. The edge-to-origin hop was plain
HTTP while the origin was a container group with no TLS listener; since 1.0.0.156 it is
HTTPS, on the certificate Azure manages for `azurewebsites.net`, so the chain is
encrypted end to end.

## Why not Front Door today

The free trial refuses to create it, and that was measured rather than assumed
(see ADR: Deployment strategy). The registrar also refuses nameserver changes, which rules out
Cloudflare's free tier until the domain can transfer, earliest late October
2026. The pattern survived both walls. Only the vendor is temporary.

## What is left of the production design

Open Infrastructure (Bicep) in this menu. Until 20 September 2026
infra/main.bicep described a design nobody ran: App Service behind Azure
Front Door with the origin locked. The App Service half of it runs now, and
the file is the description of what runs: the plan, the two sites, every
setting, the identity and the registry pull. It is deployed in incremental
mode only, because the same resource group holds the databases, the registry
and the identity, and none of them is in a template on purpose (ADR: One
plan, two sites).

The rendering service's web app is the same kind of description, in
`infra/render.bicep`, on the same plan (ADR: A rendering service beside the
API). This is the whole of it: the image, the two sites it reads by name, the
deadline its reads run under, and the heap ceiling that turns a leak into a
restart instead of the plan's memory:

```bicep
resource render 'Microsoft.Web/sites@2023-12-01' = {
  name: renderName
  location: location
  kind: 'app,linux,container'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${identity.id}': {}
    }
  }
  properties: {
    serverFarmId: plan.id
    httpsOnly: true
    clientAffinityEnabled: false
    siteConfig: {
      linuxFxVersion: 'DOCKER|${renderImage}'
      alwaysOn: true
      healthCheckPath: '/healthz'
      http20Enabled: true
      minTlsVersion: '1.2'
      ftpsState: 'Disabled'
      acrUseManagedIdentityCreds: true
      acrUserManagedIdentityID: identity.properties.clientId
      appSettings: [
        { name: 'WEBSITES_PORT', value: '8080' }
        { name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE', value: 'false' }
        // Each site by name, read at its origin: the edge's rule for each domain names the path.
        { name: 'YARD_SITES', value: 'sql=https://${toLower(sqlSite)}.azurewebsites.net,cosmos=https://${toLower(cosmosSite)}.azurewebsites.net' }
        { name: 'YARD_DEADLINE_MS', value: '2500' }
        // Node's heap held under the measured peak's ceiling, so a leak ends in a restart and not in the plan's memory.
        { name: 'NODE_OPTIONS', value: '--max-old-space-size=192' }
      ]
    }
  }
}
```
*Live from [`infra/render.bicep`](https://github.com/SteveStout/TheYard/blob/1d6b50b/infra/render.bicep#L47-L81), region `render-site`, read from this build at 1d6b50b.*

What is left is Front Door and the origin lock, behind one parameter that
defaults off. The origins are reachable directly today, as the container
groups were, and what that does and does not expose is on the Security page.
When the subscription allows Front Door it is one parameter, and the domain
layer means the public URL does not change in the switch, as it did not
change in this one.

## Files

- [`infra/main.bicep`](https://github.com/SteveStout/TheYard/blob/main/infra/main.bicep) and [`infra/appservice.bicep`](https://github.com/SteveStout/TheYard/blob/main/infra/appservice.bicep): what runs, the plan and
  the two sites (served above as Infrastructure (Bicep)).
- [`infra/render.bicep`](https://github.com/SteveStout/TheYard/blob/main/infra/render.bicep): the rendering service's web app, on the same plan.
- [`render/Dockerfile`](https://github.com/SteveStout/TheYard/blob/main/render/Dockerfile): the rendering service's image: the build on Node, nothing else.
- [`infra/aci-theyard.yaml`](https://github.com/SteveStout/TheYard/blob/main/infra/aci-theyard.yaml) and [`infra/aci-theyard-cosmos.yaml`](https://github.com/SteveStout/TheYard/blob/main/infra/aci-theyard-cosmos.yaml): the two
  container groups that ran until 1.0.0.156, stopped and kept as the way back.
- [`infra/cosmos/`](https://github.com/SteveStout/TheYard/tree/main/infra/cosmos): the container definitions the second store is built from.
- [`netlify.toml`](https://github.com/SteveStout/TheYard/blob/main/netlify.toml) and [`edge/_redirects`](https://github.com/SteveStout/TheYard/blob/main/edge/_redirects): the HTTPS edge.
- [`Dockerfile`](https://github.com/SteveStout/TheYard/blob/main/Dockerfile): the image both sites run.
- [`.github/workflows/deploy.yml`](https://github.com/SteveStout/TheYard/blob/main/.github/workflows/deploy.yml) and [`.github/workflows/deploy-render.yml`](https://github.com/SteveStout/TheYard/blob/main/.github/workflows/deploy-render.yml): how a merge becomes a roll of the sites, and of the service.
