Server upkeep scripts for my server

appwrite-server Container Setup#

Overview#

Incus container running Ubuntu 24.04 LTS with Docker, Tailscale, and nftables firewall. Has its own public IP (88.216.2.138) via macvlan on the host's physical interface.

Network#

Interface Address Purpose
eth0 (colo-net) 10.50.0.217 (DHCP, pinned) Internal communication, default outbound
eth1 (macvlan on enp1s0) 88.216.2.138/29 Public IP, inbound + source-routed outbound
tailscale0 100.101.130.16 Tailscale VPN

Traffic flow#

  • Inbound to 88.216.2.138: arrives directly on eth1 via macvlan (ISP gateway ARPs for .138, macvlan responds with its own MAC). No NAT or host involvement.
  • Outbound from 88.216.2.138: policy routing (ip rule from 88.216.2.138 table 100) sends traffic out eth1 (macvlan) directly to gateway 88.216.2.137. Public IP visible as 88.216.2.138.
  • Outbound (default): traffic not bound to .138 goes via eth0 → colo-net → host masquerade → appears as 194.26.223.226.
  • Host can reach container: via colo-net (10.50.0.217), NOT via the public IP (macvlan host isolation).

Container netplan files#

  • /etc/netplan/10-lxc.yaml: eth0 DHCP on colo-net
  • /etc/netplan/50-public.yaml: eth1 static IP + policy routing (table 100)

Host-side config for this container#

  • Incus profile: public-ip (provides macvlan NIC eth1 on enp1s0)
  • DHCP reservation: incus config device set appwrite-server eth0 ipv4.address=10.50.0.217
  • Host route: 88.216.2.138/32 via 10.50.0.217 dev colo-net (in public-ip-routes.service)

Access#

  • SSH: ssh 100.101.130.16 (Tailscale only, bound to Tailscale IP)
  • Tailscale SSH: enabled (tailscale up --ssh)
  • Incus exec: incus exec appwrite-server -- bash

Installed Software#

  • Docker CE 29.3.0 + Compose plugin + BuildX
  • Tailscale (with SSH enabled)
  • OpenSSH server (listening on Tailscale IP only)
  • nftables firewall

Incus Config#

  • Storage pool: ssd-btrfs (migrated from ssdpool via migrate-to-btrfs.sh)
  • Network: colo-net (10.50.0.0/24, NAT enabled)
  • Profiles: default, public-ip
  • Security settings (for Docker-in-LXC):
    • security.nesting: true
    • security.syscalls.intercept.mknod: true
    • security.syscalls.intercept.mount: true
    • security.syscalls.intercept.mount.allowed: overlay,shiftfs,tmpfs,proc,sysfs
    • security.syscalls.intercept.setxattr: true

Firewall (inside container)#

Config: /etc/nftables.conf

policy drop (input), accept (forward, output)

Allowed inbound:
- ct state established,related
- loopback
- ICMP
- tailscale0 (full trust - SSH, management)
- docker0, br-* (Docker bridge traffic)
- tcp/80  (HTTP)
- tcp/443 (HTTPS)
- tcp/25  (SMTP - Appwrite email)

Everything else: rate-limited log + drop

Deploying Appwrite#

ssh 100.101.130.16   # or: incus exec appwrite-server -- bash

# Follow Appwrite self-hosted install:
docker run -it --rm \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
  --entrypoint="install" \
  appwrite/appwrite:latest

# Or manually with docker compose:
# https://appwrite.io/docs/advanced/self-hosting

Point DNS A record to 88.216.2.138 for your Appwrite domain.