# VPS Server Structure — Context Prompt for Deployments

*Paste this at the start of any new chat where you're deploying or configuring something on the VPS.*

---

## Machine

IONOS VPS, type VPS 6-8-240 (6 vCore, 8 GB RAM, 240 GB NVMe)
OS: Debian 13 (trixie)
User: `patsy`
Domain: `bujour.de` / `bujour.info`

## Directory layout

```
/home/patsy/
├── stacks/          # Docker Compose stacks for infrastructure
│   ├── caddy/       # Reverse proxy
│   │   ├── compose.yaml
│   │   └── Caddyfile
│   └── forgejo/     # Git server
└── apps/            # Application source / runtime dirs
    └── checkpoint/  # CheckPoint Ehrenamt app
```

## Running containers

| Container  | Image                          | Ports                        | Network |
|------------|-------------------------------|------------------------------|---------|
| caddy      | caddy:2                        | 80, 443, 443/udp             | proxy   |
| forgejo    | codeberg.org/forgejo/forgejo:10 | 3000 (internal), 2222 (SSH) | proxy   |
| checkpoint | checkpoint-checkpoint          | 8000 (internal)              | proxy   |

## Networking

- External Docker network: `proxy` — all containers that need to be reachable via Caddy must join this network
- Caddy is the only container with public-facing ports (80/443)
- App containers expose ports internally only (no `host:container` mapping needed)

## Caddy routing (~/stacks/caddy/Caddyfile)

```
git.bujour.de {
    reverse_proxy forgejo:3000
}

cpe.bujour.info {
    reverse_proxy checkpoint:8000
}
```

## Adding a new app — the pattern

1. Create `~/apps/<appname>/` for app source/config
2. Create `~/stacks/<appname>/compose.yaml` — join network `proxy`, expose port internally only
3. Add a block to `~/stacks/caddy/Caddyfile`: `subdomain.domain.tld { reverse_proxy <appname>:<port> }`
4. Reload Caddy: `docker exec caddy caddy reload --config /etc/caddy/Caddyfile`
5. Restart new stack: `cd ~/stacks/<appname> && docker compose up -d`

## Caddy compose (~/stacks/caddy/compose.yaml)

```yaml
services:
  caddy:
    image: caddy:2
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy

volumes:
  caddy_data:
  caddy_config:

networks:
  proxy:
    external: true
```

## Shell & scripting rules

- Interactive: Bash + fzf (fuzzy history: Ctrl+R, files: Ctrl+T, dirs: Alt+C)
- All scripts touching the VPS: Bash/POSIX only — never Fish
- `sudo` required for most system commands (patsy is not root)
- `ufw` requires `sudo` — `/usr/sbin/` not in default PATH by design

## Firewall

- UFW active on VPS
- IONOS hardware firewall also configured (both must be opened for new ports)
- IONOS web console = emergency recovery path — always keep it accessible

## Hard rules (learned the hard way)

- Always seed the DB and create the first admin user before considering a deploy done
- Always verify SSH key access before applying any hardening
- New ports need opening in **both** UFW and the IONOS firewall panel
