Deployed 2026-07-19 on Orange Pi 5 (RK3588S, 4GB, Armbian/Debian 12). Public at https://piefed.your-domain.com/ (example)
Prerequisites
- Orange Pi 5 (or any Linux ARM64/x86 host)
- Docker + Docker Compose (v2)
- Domain/subdomain pointing to a Cloudflare tunnel (or any reverse proxy)
Step 1 — Install Docker
# Add Docker's GPG key and repo (Debian/Ubuntu)
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add user to docker group
sudo usermod -aG docker $USER
# Log out and back in for group change to take effect
Step 2 — Clone PieFed
git clone https://codeberg.org/rimu/pyfedi.git ~/code/python/pyfedi
cd ~/code/python/pyfedi
Step 3 — Configure Environment
cp .env.docker.sample .env.docker
Edit .env.docker:
SECRET_KEY— Generate withopenssl rand -base64 42SERVER_NAME— Set to your domain:piefed.your-domain.comPOSTGRES_PASSWORD— Change from default
The sample DATABASE_URL points to the Docker-internal db hostname (container name). This is correct — let Docker run its own PostgreSQL, don’t try to reuse the host’s.
Step 4 — Create Required Directories
mkdir -p logs media tmp
The compose file maps these as bind mounts for media uploads, logs, and temp files.
Step 5 — Fix Permissions (ARM64 gotcha)
The stock compose.yaml has user: "1000:1000" on the db service. This breaks on ARM64 because the container’s postgres user can’t write to its own data directory with that override.
Remove the user line from the db service in compose.yaml:
sed -i '/^ user: "1000:1000"/d' compose.yaml
This lets PostgreSQL run as its default container user, which can write to /var/lib/postgresql/data.
Step 6 — Build and Start
cd ~/code/python/pyfedi
sudo docker compose up --build -d
This builds three custom images (pyfedi-web, pyfedi-celery, pyfedi-piefednotifs) and pulls postgres:17, redis:6.2, and adminer.
On first startup, the web container runs database migrations automatically (Alembic), pulls the banned-instances list, and generates community data.
Services and their ports:
| Container | Host Port | Internal Port | Purpose |
|---|---|---|---|
piefed_app1 |
8030 | 5000 | Flask web app (Gunicorn) |
piefed_notifs |
8040 | 8000 | Async notifications (Uvicorn) |
piefed_celery1 |
— | — | Background task worker |
pyfedi-db-1 |
— | 5432 | PostgreSQL 17 (internal) |
pyfedi-redis-1 |
— | 6379 | Redis 6.2 (internal) |
pyfedi-adminer-1 |
127.0.0.1:8888 | 8080 | DB admin UI |
Step 7 — Create Admin Account
The web UI won’t serve pages until a site record exists. Run the interactive setup:
cd ~/code/python/pyfedi
sudo docker compose exec web bash -c "export FLASK_APP=pyfedi.py && flask init-db"
You’ll be prompted for:
- Admin username (avoid
admin) - Email address
- Password
This creates the site record, admin user, and populates the banned-instances list from the No-QAnon blocklist.
Step 8 — Verify Locally
curl -s -o /dev/null -w "HTTP %{http_code}" http://localhost:8030/
# Should return 302 (redirect to front page)
Step 9 — Expose via Cloudflare Tunnel
DNS Record
The subdomain must have a DNS record pointing to Cloudflare. In Cloudflare Dashboard → DNS → Records, add a CNAME for your PieFed subdomain targeting the tunnel.
Example (if using tunnel CNAME target):
Type: CNAME
Name: piefed
Target: your-tunnel-id.cfargotunnel.com
Proxy: Proxied (orange cloud)
Alternatively, point it to another working subdomain like web.your-domain.com — Cloudflare will route it through the same tunnel.
Tunnel Ingress Rule
In Cloudflare Zero Trust → Networks → Tunnels → select your tunnel, add an ingress rule:
| Hostname | Origin Service |
|---|---|
piefed.your-domain.com |
http://localhost:8030/ |
DNS propagation can take a minute or two. Once it resolves, https://piefed.your-domain.com/ works.
Management Commands
# Start/stop
sudo docker compose up -d
sudo docker compose down
# View logs
sudo docker compose logs -f # all services
sudo docker compose logs -f web # just the web app
# Restart a service
sudo docker compose restart web
# Update to latest version
git pull
sudo docker compose down
sudo docker compose up --build -d
# Then re-run flask init-db if schema changed
# Access adminer (DB web UI)
# Open http://localhost:8888/ in browser (Pi desktop only)
# Server: db, User: piefed, Password: piefed, Database: piefed
Architecture Notes
- PieFed uses its own PostgreSQL and Redis containers — completely separate from any host-level services (Polaris, etc.). No port conflicts because the DB/Redis containers only listen on the internal Docker bridge network.
- No Nginx reverse proxy needed on the Pi — Docker exposes port 8030 directly. The Cloudflare tunnel connects to that.
- 1.7 GB image footprint — the three custom images (web, celery, notifs) share a base Python 3.13 layer, plus postgres:17 and redis:6.2.
- Build cache consumes ~2.7 GB during initial build but can be pruned later with
docker builder prune.
