2026/01/24 - LAN Bitwarden + Pi-hole on a PoE Raspberry Pi (Docker)¶
This post documents my full setup of Bitwarden Lite and Pi-hole on a PoE Raspberry Pi connected to a switch, so it’s always-on. The objective was to keep everything LAN-only, use friendly internal DNS names like bitwarden.home.arpa, and avoid exposing anything publicly.
Hardware and environment used:
- Raspberry Pi (PoE, always on)
- Debian-based OS on the Pi
- Docker + Docker Compose v2
- Router: Virgin Media Hub 4
- LAN IP for the Pi:
192.168.*.*** - Fedora workstation used for testing
1. Installing Docker Compose properly (and avoiding a classic trap)¶
I already had Docker installed and working:
docker --version
````
I attempted to install an old `docker-compose` binary manually using a GitHub release link:
```bash
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
This failed with:
/usr/local/bin/docker-compose: line 1: Not: command not found
The download was only 9 bytes, which strongly indicates GitHub returned a Not Found response instead of a binary. Lesson learned:
Do not install old docker-compose binaries manually unless you verify the URL and architecture.
Correct fix: Docker Compose v2 plugin¶
Modern Docker uses the Compose plugin (docker compose, space not dash):
sudo rm -f /usr/local/bin/docker-compose
sudo apt-get update
sudo apt-get install -y docker-compose-plugin
docker compose version
2. Bitwarden: don’t clone bitwarden/server¶
I initially cloned Bitwarden’s server repo:
mkdir ~/bitwarden
cd ~/bitwarden
git clone https://github.com/bitwarden/server.git .
docker compose up -d
This failed with:
no configuration file provided: not found
Cloning bitwarden/server is not the normal self-host method. Instead, for ARM homelabs, the cleanest approach is:
Bitwarden Lite (container-first, simple, supports homelab use)
So I removed the wrong repo:
rm -rf ~/bitwarden
3. Deploy Bitwarden Lite with Docker Compose¶
Create a working directory:
mkdir -p ~/bitwarden-lite
cd ~/bitwarden-lite
Create settings.env (this is important):
BW_DOMAIN=bitwarden.home.arpa
BW_INSTALLATION_ID=<your real ID>
BW_INSTALLATION_KEY=<your real key>
BW_DB_PROVIDER=sqlite
Bitwarden Lite requires a real Installation ID and Installation Key obtained from Bitwarden’s hosting page. Placeholders will cause backend crashes and registration failures.
Create docker-compose.yml:
services:
bitwarden:
image: ghcr.io/bitwarden/lite
restart: always
env_file:
- settings.env
volumes:
- bwdata:/etc/bitwarden
ports:
- "8080:8080"
volumes:
bwdata:
Start the container:
docker compose up -d
docker ps
Bitwarden Lite is now reachable on:
http://192.168.*.***:8080
4. Choosing a DNS domain: home.arpa¶
I originally wanted names like:
bitwarden.rorypve.rory
But using fake TLDs can cause name collisions and odd behavior. Instead, the correct standard domain for home networks is:
home.arpa
So the final naming scheme became:
bitwarden.home.arpapihole.home.arpa- (future)
pve.home.arpa,nas.home.arpa, etc.
5. Adding Pi-hole on the same Pi (Virgin Hub 4 limitation)¶
Virgin Media Hub 4 makes it easy to reserve an IP via DHCP, but it does not reliably support pushing a custom DNS server to LAN clients the way a “real” router does. Pi-hole solves this at the network level.
Before installing Pi-hole, I checked if port 53 was free:
sudo ss -ltnup | grep ':53' || true
sudo ss -lnup | grep ':53' || true
Only Avahi on port 5353 was present, which is fine. DNS port 53 was available.
Pi IP state¶
The Pi showed:
inet 192.168.*.***/24 ... dynamic
Meaning it was still using DHCP. That’s fine initially, but before making Pi-hole the DHCP server later, a truly static IP is required.
6. Running Pi-hole with Docker (host networking)¶
Pi-hole works best with network_mode: host if DHCP will be enabled later. It also avoids Docker bridge limitations for DHCP.
Update docker-compose.yml to include Pi-hole:
services:
bitwarden:
image: ghcr.io/bitwarden/lite
restart: always
env_file:
- settings.env
volumes:
- bwdata:/etc/bitwarden
ports:
- "8080:8080"
pihole:
container_name: pihole
image: pihole/pihole:latest
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
environment:
TZ: "Europe/London"
FTLCONF_webserver_api_password: "CHANGE_THIS_PASSWORD"
FTLCONF_dns_listeningMode: "ALL"
FTLCONF_webserver_port: "8081o,[::]:8081o"
volumes:
- ./etc-pihole:/etc/pihole
volumes:
bwdata:
Create the Pi-hole data directory:
mkdir -p ./etc-pihole
Restart the stack:
docker compose down
docker compose up -d
docker ps
Pi-hole UI is now available at:
http://192.168.*.***:8081/admin
7. Pi-hole admin password (and how it behaves)¶
Pi-hole asked for an admin password, but it wasn’t printed in the terminal because the stack was started detached.
Trying to set it interactively with:
docker exec -it pihole pihole setpassword
returned:
webserver.api.password set by environment variable. Please unset it to use this function
Lesson learned:
If you set FTLCONF_webserver_api_password in Compose, Pi-hole will require that password and block interactive password changes.
Shell history warning¶
If you ever set a password as a CLI argument, it will be written to shell history. To remove it:
history
history -d <LINE_NUMBER>
history -w
8. Creating LAN DNS records inside Pi-hole¶
In the Pi-hole UI:
Local DNS → DNS Records
Add:
pihole.home.arpa→192.168.*.***bitwarden.home.arpa→192.168.*.***
This may feel odd because both names point to the same IP. That is normal. DNS just returns an IP address; the port or proxy decides which service you get.
At this stage:
- Pi-hole:
http://pihole.home.arpa:8081/admin - Bitwarden:
http://bitwarden.home.arpa:8080
9. Fedora testing: why DNS broke with Proton VPN¶
My Fedora workstation was used to test Pi-hole resolution. With Proton VPN enabled, DNS failed even though Pi-hole was correct.
resolvectl status showed Proton’s interface (proton0) had:
- DNS domain:
~.(catch-all) - DNS server:
10.2.0.1
Meaning:
all DNS queries were going through Proton VPN DNS, not my LAN Pi-hole.
To confirm LAN routing still worked, I ran:
ip route get 192.168.*.***
It correctly routed via my LAN interface (not via the VPN), so the issue was DNS only.
Fix: split DNS only for home.arpa¶
This preserves VPN DNS for internet domains, but forces home.arpa queries to Pi-hole:
sudo resolvectl dns enp0s13f0u3u2c2 192.168.*.***
sudo resolvectl domain enp0s13f0u3u2c2 '~home.arpa'
sudo resolvectl flush-caches
After that:
resolvectl query pihole.home.arpa
resolvectl query bitwarden.home.arpa
Both returned 192.168.*.*** even with the VPN enabled.
10. Bitwarden registration error: “Unhandled server error”¶
Even when the Bitwarden web UI loaded, creating an account initially failed with a red popup:
- “Unhandled server error occurred”
This happened for two reasons during troubleshooting:
Bad Installation ID / Key causes backend crashes¶
Using placeholders or malformed values led to SIGABRT crashes in the container logs, and registration could never work:
docker logs -f bitwarden-lite-bitwarden-1
Correct fix: request valid ID+Key from Bitwarden and paste them into settings.env.
Startup warm-up delay¶
Even once services showed as “RUNNING”, registration sometimes failed briefly while the stack finished initialization. After a short wait, it started working without any config changes.
11. Final working state¶
The final LAN-only services:
-
Pi-hole web UI:
-
https://pihole.home.arpa/admin -
Bitwarden Lite:
-
https://bitwarden.home.arpa
And DNS-based service discovery works across the LAN via Pi-hole local DNS.
Summary¶
Key lessons from this build:
- Use Docker Compose v2 (
docker compose) instead of olddocker-composebinaries. - Don’t clone
bitwarden/server; use Bitwarden Lite or the official installer. - Use
home.arpafor internal naming. - Pi-hole local DNS makes homelab hostnames clean and scalable.
- VPNs often “own” DNS; split DNS fixes local resolution without breaking VPN security.
- Bitwarden Lite needs real Installation ID/Key and sometimes a short warm-up before registration works.