# 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 ```bash 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.