Add server deployment guide, bind port to localhost

DEPLOY.md: dedicated user with rootless Docker, deploy key, nginx + HTTPS,
updates, backups. The container port is only published on 127.0.0.1 so
nginx is the only way in (Docker would otherwise bypass the firewall).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
Claude
2026-10-20 20:54:20 +00:00
parent a2e719afd0
commit 2ae64420d9
3 changed files with 126 additions and 2 deletions
+120
View File
@@ -0,0 +1,120 @@
# Running it on a Linux server
Goal: the app runs as its own unprivileged user, only nginx can reach it, and everything is behind HTTPS.
## 1. Dedicated user with rootless Docker
Members of the `docker` group are effectively root on the machine, so do **not** add the new user to it. Use *rootless* Docker instead: the daemon, the images and the container processes all belong to that one user. If something breaks out of the container, it only gets that user's rights. This runs separately from any existing (root) Docker, for example the one AMP uses.
```bash
# as an admin user
sudo adduser --disabled-password --gecos "" awcc # no password, no sudo
sudo apt install -y uidmap dbus-user-session slirp4netns docker-ce-rootless-extras
sudo loginctl enable-linger awcc # user services start at boot
```
Leave the system Docker alone if other things (AMP) use it; rootless Docker runs next to it.
`docker-ce-rootless-extras` comes from Docker's own apt repository. If Docker was installed from your distribution (`docker.io`), add Docker's repository first (see docs.docker.com/engine/install).
Switch to the user and install the rootless daemon:
```bash
sudo -iu awcc
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
echo 'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock' >> ~/.bashrc
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
source ~/.bashrc
docker run --rm hello-world # test
```
## 2. Get the code
Use a read-only **deploy key** so this user never holds your personal Gitea credentials:
```bash
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "awcc deploy key"
cat ~/.ssh/id_ed25519.pub # Gitea: repository -> Settings -> Deploy Keys -> add, leave "write access" off
git clone ssh://git@git.bitrux.de/otto/AWCharacterCreator.git # adjust the SSH port/URL to your Gitea
cd AWCharacterCreator
```
## 3. Configure and start
```bash
cp .env.example .env
nano .env # set AW_REGISTRATION_CODE, and AW_TRUST_PROXY=1 (nginx is in front)
chmod 600 .env
docker compose up -d --build
curl -sI http://127.0.0.1:8080 | head -1 # should print HTTP/1.1 200 OK
```
The container only listens on `127.0.0.1:8080`, so it is not reachable from outside until nginx forwards to it. The database is `./db/aw.db`.
## 4. nginx + HTTPS
Point a DNS name (for example `aw.yourdomain.tld`) at the server, then create `/etc/nginx/sites-available/aw`:
```nginx
server {
listen 80;
server_name aw.yourdomain.tld;
client_max_body_size 1m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr; # overwrite, so clients cannot fake their address
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
```
```bash
sudo ln -s /etc/nginx/sites-available/aw /etc/nginx/sites-enabled/aw
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d aw.yourdomain.tld # gets the certificate, adds HTTPS and the redirect
```
Then add inside the HTTPS `server` block: `add_header Strict-Transport-Security "max-age=31536000" always;`
Firewall: only SSH, 80 and 443 should be open. Port 8080 is not published to the outside at all.
Open the site, choose "Create account" and register with your registration code.
## 5. Updating
```bash
cd ~/AWCharacterCreator
git pull
docker compose up -d --build # only needed when code changed
```
If only `backend/data/rules.json` changed, `git pull` is enough (it is re-read on every request).
## 6. Backups
```bash
sudo apt install -y sqlite3
mkdir -p ~/backups
crontab -e
# add:
0 3 * * * sqlite3 $HOME/AWCharacterCreator/db/aw.db ".backup '$HOME/backups/aw-$(date +\%F).db'" && find $HOME/backups -name 'aw-*.db' -mtime +14 -delete
```
Copy `~/backups` somewhere off the server now and then.
## 7. Handy commands
```bash
docker compose logs -f # logs
docker compose exec awcc python -m app.admin list # users
docker compose exec awcc python -m app.admin passwd NAME # reset a password
docker compose down # stop
```
If `docker` says it cannot connect to the daemon, check the two `export` lines from step 1 and `systemctl --user status docker`.
+1 -1
View File
@@ -12,7 +12,7 @@ Website for creating characters for the Arcane Wasteland TTRPG, with a searchabl
docker compose up -d --build
```
Then open `http://<server>:8080`. Users, characters and presets are stored in `./db/aw.db`.
Then open `http://localhost:8080` (the port is bound to the machine itself; see **DEPLOY.md** for running it on a server with its own user, nginx and HTTPS). Users, characters and presets are stored in `./db/aw.db`.
### Login and accounts
+5 -1
View File
@@ -3,7 +3,11 @@ services:
build: .
restart: unless-stopped
ports:
- "8080:8000" # change the host port, or put the container behind a reverse proxy
# Only reachable from the server itself; the reverse proxy (nginx) is the public entrance.
# Docker bypasses firewall rules for published ports, so never publish this on 0.0.0.0.
- "127.0.0.1:8080:8000"
security_opt:
- no-new-privileges:true
environment:
# Code that new users must enter to create an account (set it in the .env file next to this file).
# Empty = registration is disabled.