Files
homelab/hosts/mars/luna-sites-README.md
T
2026-09-18 20:39:06 +02:00

4.0 KiB

Hosting your web apps on mars

You can run web apps as containers and publish them on the home network at http://mars.sol/<name>/, without anyone changing mars's configuration. Everything below takes effect immediately — no restart, no redeploy.

This file is mounted read-only and is rewritten on every restart. Save what you need from it to your memory.

How it fits together

  • podman in your shell does not run containers next to you. It talks, through $CONTAINER_HOST, to a separate unprivileged account on mars (luna-apps). Containers there keep running when you restart, and come back after mars reboots if they were started with --restart=always.
  • Caddy on mars routes http://mars.sol/<name>/ to the port you name in /opt/data/sites/<name>.json. A service on mars checks that file and writes the outcome to /opt/data/sites-status.txt.

Publish an app

  1. Put the source under /opt/data/apps/<name>/ with a Containerfile (or Dockerfile), and build it. The directory is uploaded, so this works from where you are:

    podman build -t localhost/<name> /opt/data/apps/<name>
    
  2. Run it. Publish its port on 127.0.0.1 only, using a host port between @portMin@ and @portMax@ that no other app uses (podman ps shows the taken ones):

    podman run -d --name <name> --restart=always \
      -p 127.0.0.1:20001:8080 localhost/<name>
    
  3. Register it:

    echo '{"port": 20001}' > /opt/data/sites/<name>.json
    
  4. Check that it took, then fetch it:

    cat /opt/data/sites-status.txt
    curl -si http://127.0.0.1/<name>/
    

    It is now at http://mars.sol/<name>/ for anyone on the home network.

Rules the registry enforces

  • <name> is lowercase letters, digits and -, starts with a letter or digit, at most 32 characters. The file is /opt/data/sites/<name>.json.
  • The file holds exactly one JSON object, and only port is read.
  • port is an integer from @portMin@ to @portMax@. Anything else is rejected (that includes everything else already running on mars).
  • A rejected entry never affects the others. sites-status.txt says why.
  • If sites-status.txt starts with ERROR, that is a fault on mars's side, not in your entry — tell darman.

Writing apps that work under //

Caddy strips /<name> before the request reaches your app, so the app itself sees /, /style.css, /api/items. The browser, however, is at http://mars.sol/<name>/, so every link, asset URL and fetch() in the page must keep that prefix:

  • Prefer relative URLs: style.css, ./api/items — not /style.css.
  • Or set the framework's public base URL to /<name>/ (e.g. Vite's base). Avoid settings that ALSO expect the prefix on incoming requests (Next.js basePath); the prefix has already been removed by then.
  • The original prefix arrives in the X-Forwarded-Prefix header.
  • http://mars.sol/<name> redirects to http://mars.sol/<name>/.

Files and data

  • -v /opt/data/...:/somewhere does not work: those paths exist only inside your container, and luna-apps cannot see your files. Copy code into the image in the Containerfile.
  • Keep an app's state in a named volume: -v <name>-data:/data.
  • Pulling public images works (podman pull docker.io/library/nginx).
  • Do not copy tokens or anything else from /opt/data into an app. The apps cannot read your files; keep it that way.

Update, inspect, remove

  • Update: rebuild, podman rm -f <name>, run it again on the same port. The JSON file stays as it is.
  • Inspect: podman ps -a, podman logs <name>, cat /opt/data/sites-status.txt.
  • Remove: rm /opt/data/sites/<name>.json, then podman rm -f <name>, and optionally podman rmi localhost/<name> and podman volume rm <name>-data.

Limits

  • Home network only: plain http://, not reachable from the internet, not on mgaction.town.
  • There is no login in front of these apps. Anyone on the home network can use them, so do not publish anything that would be a problem to expose there.