Project Overview: Implementation of a high-security media server environment. This project focuses on transforming a Proxmox host into a NAT gateway and deploying a micro-services stack (Immich) using Docker, protected by LUKS encryption and advanced volume management.
1. Network & Routing Configuration
Our architecture isolates the application layer from the public network through a dual-bridge system, managed by kernel-level routing and a custom firewall.
Phase A: Bridge Definition
We configured /etc/network/interfaces to separate the WAN (School network) from the internal LAN (Container network):
Bash
auto lo
iface lo inet loopback
auto eno1
iface eno1 inet manual
auto vmbr0
iface vmbr0 inet static
address 10.0.24.105/24
gateway 10.0.24.254
bridge-ports eno1
bridge-stp off
bridge-fd 0
auto vmbr1
iface vmbr1 inet static
address 192.168.1.254/24
bridge-ports none
bridge-stp off
bridge-fd 0
Phase B: Kernel Routing & Optimization
To allow traffic to flow between bridges and harden the host, we applied parameters in /etc/sysctl.d/99-firewall.conf:
Bash
net.ipv4.ip_forward = 1
net.ipv6.conf.default.forwarding = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.default.proxy_arp = 0
net.ipv4.conf.all.rp_filter = 1
kernel.sysrq = 1
net.ipv4.conf.default.send_redirects = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_all = 1
vm.swappiness = 0
Phase C: Firewall & NAT Logic (nftables)
The script /etc/firewall_Host.sh defines the security rules, using nftables for high-performance filtering and dynamic NAT rules.
Bash
wan_if='vmbr0'
lxc_if='vmbr1'
iptables -F
iptables -t nat -F
nft flush ruleset
nft -f - <<EOF
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept
ct state established,related accept
iifname "$lxc_if" accept
iifname "$wan_if" tcp dport { 80, 443, 8006, 22224, 9221 } accept
iifname "$wan_if" udp dport 53 accept
}
chain forward {
type filter hook forward priority 0; policy accept;
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
$(grep "PREROUTING" /etc/init.d/MyNatRules.config | sed 's/iptables -t nat -A PREROUTING -i/iif/g' | sed 's/-p tcp //g' | sed 's/--dport/tcp dport/g')
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oif "$wan_if" masquerade
}
}
EOF
Phase D: Persistence (Systemd)
To ensure rules persist after a reboot, we created a service in /etc/systemd/system/firewall.service:
Ini, TOML
[Unit]
Description=Script de Firewall et NAT pour les CT LXC
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash /etc/firewall_Host.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
2. Immich Deployment via Docker
Inside an LXC container (ID 141), we deployed the Immich stack. By modifying the .env file, we redirected media storage to a secure mount point.
Bash
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
mkdir -p ~/immich-app && cd ~/immich-app
wget https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget https://github.com/immich-app/immich/releases/latest/download/example.env -O .env
sed -i 's|UPLOAD_LOCATION=./library|UPLOAD_LOCATION=/mnt/immich_data|' .env
docker compose up -d
3. Volume Management, Encryption & Privileges
This phase focuses on creating a « Zero-Knowledge » storage layer where data is encrypted on the physical host but remains accessible to the container through a secure bridge.
- Step 1: LVM & Infrastructure Activation: Proxmox manages storage as Logical Volumes. Before any operation, we ensure the volumes are active and visible to the system kernel using
vgchange -ay pve. - Step 2: LUKS Encryption Layer: We initialized a LUKS partition on the logical volume. This adds an industry-standard encryption layer. Once initialized, the volume is « opened, » creating a virtual device in
/dev/mapper/immich_crypt. All data written to this device is encrypted before it hits the physical disk.Bashcryptsetup luksFormat /dev/pve/vm-141-disk-2 cryptsetup open /dev/pve/vm-141-disk-2 immich_crypt - Step 3: Filesystem & Permissions (The « Privilege Gap »): We formatted the decrypted volume with Ext4. However, since we are using unprivileged LXC containers for security, the container’s « root » user does not have the same ID as the host’s root. On the host, the container’s root is mapped to UID 100000. To allow Immich to save photos, we must recursively change the ownership of the host mount point to this specific ID.Bash
mkfs.ext4 /dev/mapper/immich_crypt mount /dev/mapper/immich_crypt /mnt chown -R 100000:100000 /mnt/media - Step 4: Bind Mounting: Finally, we create a permanent bridge using Proxmox’s
pct setcommand. This « plugs » the host’s decrypted folder directly into the container’s/mnt/immich_datadirectory.Bashpct set 141 -mp0 /dev/mapper/immich_crypt,mp=/mnt/immich_data
4. Troubleshooting: Encountered Obstacles
During implementation, we faced two major hurdles requiring deep system analysis:
- Problem 1: Root Partition Saturation: Docker images are voluminous, and the container’s root partition quickly reached 95% capacity. We used
df -hto diagnose the leak and confirmed that moving the heavy media library to the encrypted external volume was the only viable long-term solution. - Problem 2: Routing Conflicts (Connection Refused): Even with the firewall open, we could not access the web interface. We discovered that Docker’s internal
iptablesrules inside the container were conflicting with the NAT routing of the Proxmox host. We solved this by flushing the internal container rules:Bashiptables -F iptables -t nat -F iptables -P INPUT ACCEPT
5. Conclusion
This architecture demonstrates a professional approach to data privacy. By decoupling network routing (nftables), application management (Docker), and at-rest encryption (LUKS), we built a system that protects sensitive media while remaining highly scalable.
No responses yet