# Deploy a .NET MAUI app as a website

> Canonical page: https://mauiweb.appmare.com/deploy.html  
> Language: English (en)  
> Purpose: How to deploy a MauiWeb site: host requirements, a NativeAOT Dockerfile, Google Cloud Run, Azure App Service, a Linux server with systemd and nginx, and domains on the license.  
> Markdown copy made 31 October 2026 · Maintained by AppMare (contact@appmare.com)  
> Site map for this guide: https://mauiweb.appmare.com/sitemap.md

Put your MauiWeb site live on Azure App Service, Google Cloud Run or your own Linux server. Updated 31 October 2026.

## What every host needs

A MauiWeb site is an ordinary ASP.NET Core app, so it runs anywhere ASP.NET Core runs. Four things matter:

- **WebSockets** allowed through every proxy, for `/_maui/ws`.
- **Sticky sessions** when you run more than one instance: each visitor's app lives on one instance, and their socket must reach it.
- **Long-lived connections:** raise proxy and platform timeouts. If a socket is cut, the page reconnects to the same session within 45 seconds.
- **The real host name** forwarded to your app, so the license check sees your domain.

Add your license key as the `MAUIWEB_LICENSE_KEY` environment variable, not in your repository. Health checks can use `GET /_maui/health`.

## Build a container

Publish with NativeAOT so the server runs one native binary and no .NET assemblies. This Dockerfile sits at your solution root, next to the shared app and the web project:

*Dockerfile:*

```

FROM mcr.microsoft.com/dotnet/sdk:11.0-aot AS build
WORKDIR /src
COPY . .
RUN dotnet publish MyApp.Web/MyApp.Web.csproj -c Release -r linux-x64 -p:PublishAot=true -o /app

FROM mcr.microsoft.com/dotnet/runtime-deps:11.0
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["./MyApp.Web"]

```

Fonts, images and raw files copied into the web project's `Resources` folder at build time are published with it. See [Share your app](https://mauiweb.appmare.com/docs.html#shared-app).

## Google Cloud Run

Cloud Run builds the Dockerfile and runs the container. Turn on session affinity, raise the request timeout to the 60-minute maximum (it also limits how long a WebSocket stays open) and keep one instance warm so visitors' sessions are not lost when the service scales to zero.

*Terminal:*

```

gcloud run deploy myapp-web --source . --region us-central1 \
  --allow-unauthenticated --port 8080 \
  --session-affinity --timeout 3600 --min-instances 1 \
  --set-secrets MAUIWEB_LICENSE_KEY=mauiweb-license:latest

```

Cloud Run's session affinity is best effort, so keep per-visitor data you cannot lose in a database, as you would for any web app. Map your domain under Cloud Run's custom domains, then make sure it is on your license.

## Azure App Service

Push the image to Azure Container Registry and create a Linux web app from it in the portal or with `az webapp create`. Then turn on WebSockets, Always On and ARR affinity (sticky sessions), and set the port and key:

*Terminal:*

```

az acr build --registry myregistry --image myapp-web:1 .

az webapp config set -g my-group -n myapp-web --web-sockets-enabled true --always-on true
az webapp update -g my-group -n myapp-web --client-affinity-enabled true
az webapp config appsettings set -g my-group -n myapp-web \
  --settings WEBSITES_PORT=8080 MAUIWEB_LICENSE_KEY="MW1.…"

```

Always On needs a Basic plan or higher. Add your custom domain under Custom domains, then make sure it is on your license.

## Your own Linux server

This is how the [live gallery](https://mauiweb.appmare.com/gallery.html) runs: the NativeAOT binary as a systemd service behind nginx. Publish on a Linux machine (`dotnet publish -c Release -r linux-x64 -p:PublishAot=true -o out`) and copy `out` to the server.

*/etc/systemd/system/myapp-web.service:*

```

[Unit]
Description=MyApp website (MauiWeb)
After=network.target

[Service]
WorkingDirectory=/var/www/myapp-web
ExecStart=/var/www/myapp-web/MyApp.Web --urls http://127.0.0.1:5380
Environment=ASPNETCORE_ENVIRONMENT=Production
EnvironmentFile=/etc/myapp-web.env
Restart=always
User=www-data

[Install]
WantedBy=multi-user.target

```

*/etc/nginx/sites-available/myapp-web:*

```

map $http_upgrade $connection_upgrade { default upgrade; "" close; }

server {
    listen 443 ssl;
    server_name shop.example.com;
    # ssl_certificate lines from certbot

    location / {
        proxy_pass http://127.0.0.1:5380;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
        client_max_body_size 100m;   # FilePicker and MediaPicker uploads
    }
}

```

Put `MAUIWEB_LICENSE_KEY=…` in `/etc/myapp-web.env`, then run `systemctl enable --now myapp-web` and reload nginx. With more than one server, use a load balancer with sticky sessions in front of them. Behind Cloudflare, WebSockets work by default; uploads are limited to 100 MB per request on its Free and Pro plans.

## Domains and your license

- Your key lists the domains it covers. `example.com` and `www.example.com` count as one domain.
- On a domain the key does not cover, the site keeps working and visitors see an "Unlicensed MauiWeb" notice.
- About once a day your server reports the key's id, the domain and the MauiWeb version to our license service. Allow outgoing HTTPS from your server.
- Professional and Enterprise keys can include staging domains. See [pricing](https://mauiweb.appmare.com/pricing.html) and [license key](https://mauiweb.appmare.com/docs.html#licensing).

> Building with [AppMare](https://appmare.com)? It publishes your app as a MauiWeb site to your own Google Cloud Run or Azure App Service, license included.
