Drop folders containing static HTML websites into ./sites, and serve each one as a subdomain behind Nginx Proxy Manager.
Example:
./sites/hello/index.html -> https://hello.sites.example.com
./sites/docs/index.html -> https://docs.sites.example.com
The watcher can also create/update Homarr Apps for each folder.
static-sites: Nginx container serving/sites/<folder>based on the requested subdomain.site-watcher: Python container scanning folders and optionally syncing Homarr Apps.- Nginx Proxy Manager: you create one wildcard proxy host manually.
- DNS: you create one wildcard DNS record manually.
It intentionally avoids creating one NPM host per folder. One wildcard NPM host is simpler and more reliable.
- Docker + Docker Compose
- Existing Nginx Proxy Manager
- Existing Homarr, optional but recommended
- A domain or local DNS zone where you can create a wildcard record
cp .env.example .env
nano .envImportant values:
SITES_BASE_DOMAIN=sites.example.com
NPM_NETWORK=nginxproxymanager_default
HOST_SITES_DIR=./sitesFind the NPM Docker network with:
docker network lsIf your NPM compose project is called nginxproxymanager, the network is often nginxproxymanager_default.
If you do not already have an NPM network, create one and attach both NPM and this stack to it:
docker network create npmThen set:
NPM_NETWORK=npmdocker compose up -d --buildCheck logs:
docker compose logs -f static-sites site-watcherCreate one proxy host:
Domain Names: *.sites.example.com
Scheme: http
Forward Hostname / IP: static-sites
Forward Port: 8080
Websockets Support: disabled
SSL tab:
SSL Certificate: your wildcard certificate for *.sites.example.com
Force SSL: enabled
HTTP/2 Support: enabled
For a real public wildcard certificate, use a DNS challenge in NPM.
Create a wildcard DNS record:
*.sites.example.com -> your NPM server IP
For a local-only homelab, do this in Pi-hole, AdGuard Home, Technitium, your router, or your local DNS server.
Homarr sync is optional.
Create a Homarr API key:
Homarr -> Management -> Tools -> API -> Authentication
Then edit .env:
HOMARR_ENABLED=1
HOMARR_URL=http://homarr:7575
HOMARR_API_KEY=<id>.<token>The watcher creates missing Homarr Apps using:
GET /api/apps
POST /api/apps
DELETE /api/apps/{id} only if DELETE_REMOVED=1
New apps are created with the name Static: <folder>.
Note: depending on your Homarr version/layout, API-created Apps may still need to be placed on a board manually. Homarr currently exposes Apps through the API; full board placement automation may depend on your version.
Manual:
mkdir -p sites/my-site
cat > sites/my-site/index.html <<'HTML'
<!doctype html>
<html><body><h1>Hello</h1></body></html>
HTMLOr use the helper:
./scripts/create-site.sh my-siteThen open:
https://my-site.sites.example.com
Folder names must match:
lowercase letters, numbers, and hyphens only
Valid:
hello
my-docs
site123
Invalid:
MySite
site_name
site.example
The compose file exposes Nginx on localhost by default:
STATIC_HTTP_PORT=127.0.0.1:18080Add a temporary /etc/hosts entry:
127.0.0.1 hello.sites.example.com
Then test:
curl -H 'Host: hello.sites.example.com' http://127.0.0.1:18080/Make sure this stack and NPM are on the same Docker network.
docker network inspect nginxproxymanager_defaultYou should see both the NPM container and static-sites.
Make sure the key is in the format:
<id>.<token>
Keep:
HOMARR_AUTH_MODE=apikeyIf your installation expects bearer auth, try:
HOMARR_AUTH_MODE=beareror:
HOMARR_AUTH_MODE=bothCheck that the folder has an index.html:
sites/my-site/index.html
and that the hostname is:
my-site.sites.example.com