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(inpublic-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: truesecurity.syscalls.intercept.mknod: truesecurity.syscalls.intercept.mount: truesecurity.syscalls.intercept.mount.allowed: overlay,shiftfs,tmpfs,proc,sysfssecurity.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.