Forget GitHub Actions. Forget Cloudflare Pages. This is how we ACTUALLY deploy: 1. npm run build 2. scp dist.tar.gz pi@192.168.4.64:/tmp/ 3. docker run -d -p PORT:80 nginx:alpine 4. Done in 30 seconds Architecture: Internet → Cloudflare → Tunnel → Pi → Caddy → Docker Real example: Just deployed lucidia.earth 3D metaverse - Three.js + Cannon.js physics - Procedural cityscape + floating islands - Live at https://lucidia.earth - Deployment time: 30 seconds No tokens. No secrets. No bullshit. "The simplest system that works." 🛣️ 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
8.3 KiB
The REAL Deployment System - Pi Edition
How we actually deploy everything. No GitHub Actions. No Cloudflare Pages. No bullshit.
Architecture
Internet → Cloudflare DNS → Cloudflare Tunnel → Pi → Caddy → Docker
That's it.
The Pi (192.168.4.64)
Hardware: Raspberry Pi 4B OS: Raspberry Pi OS Hostname: aria Running: 26+ Docker containers
Active Services
lucidia-earth → port 3040 (3D Metaverse)
blackroadinc-us → port 9444
blackroad-docs → port 3050
roadchain-io → port 3031
roadcoin-io → port 3030
blackroadqi-com → port 3020
earth-blackroad-io → port 3013
caddy → reverse proxy (handles all routing)
portainer → port 9000 (container management)
The Cloudflare Tunnel
Running as system service:
/usr/bin/cloudflared tunnel run --token eyJhIjoiODQ4Y2YwYjE4ZDUxZTAxNzBlMGQxNTM3YWVjMzUwNWEiLCJ0IjoiNTI5MTU4NTktZGExOC00YWE2LWFkZDUtN2JkOWZjYWMyZTBiIiwicyI6Ik1HWXdaRE13TXpVdFpHSXdZaTAwTjJJNUxUZzRObVV0WWpOaE1XSmxNVGRsTXpSbSJ9
This creates an encrypted tunnel from Cloudflare's edge to the Pi. No port forwarding. No VPN. No firewall config.
How to Deploy ANYTHING
Step 1: Build it locally
cd ~/your-project
npm run build
# or
python -m build
# or whatever your build command is
Step 2: Copy it to the Pi
tar czf dist.tar.gz dist/
scp dist.tar.gz pi@192.168.4.64:/tmp/
Step 3: Run it in Docker
ssh pi@192.168.4.64
# Extract
cd /tmp
tar xzf dist.tar.gz
# Run container on any available port
docker run -d \
--name your-site \
--restart unless-stopped \
-p 3050:80 \
-v /tmp/dist:/usr/share/nginx/html:ro \
nginx:alpine
Step 4: Add to Caddy
# Edit Caddyfile
docker exec caddy vi /etc/caddy/Caddyfile
# Add your domain:
your-domain.com {
reverse_proxy localhost:3050
}
# Reload
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
Step 5: Add DNS Record
Go to Cloudflare dashboard:
- Type: CNAME
- Name: your-domain.com
- Content: lucidia-earth.pages.dev (or the tunnel hostname)
- Proxied: Yes
Done. That's the whole process.
Real Example: Deploying lucidia.earth Metaverse
What we did:
# Local machine
cd ~/lucidia-metaverse
npm run build
tar czf dist.tar.gz dist/
scp dist.tar.gz pi@192.168.4.64:/tmp/
# On Pi
ssh pi@192.168.4.64
cd /tmp
tar xzf dist.tar.gz
docker stop lucidia-earth
docker rename lucidia-earth lucidia-earth-old
docker run -d \
--name lucidia-earth \
--restart unless-stopped \
-p 3040:80 \
-v /tmp/dist:/usr/share/nginx/html:ro \
nginx:alpine
# Test
curl http://localhost:3040
Caddy was already configured. DNS already pointed to the tunnel. Instant deployment.
Time: 30 seconds.
Why This Works Better
GitHub Actions / Cloudflare Pages
- ❌ Requires API tokens (expire, get revoked, auth errors)
- ❌ Requires GitHub secrets management
- ❌ Requires workflow files
- ❌ Requires SHA pinning for security
- ❌ Runs on shared infrastructure (slow, unreliable)
- ❌ 5-10 minute build times
- ❌ Complex debugging when it breaks
- ❌ 58 repositories = 58 configurations
Pi Direct Deployment
- ✅ No tokens, no secrets, no auth
- ✅ No config files needed
- ✅ Runs on dedicated hardware (fast, reliable)
- ✅ 30 second deployments
- ✅ Direct SSH access for debugging
- ✅ One Pi = all 58 sites
- ✅ Can test locally before deploying
- ✅ Full control over everything
Container Management
View all containers
ssh pi@192.168.4.64
docker ps
View logs
docker logs lucidia-earth --tail 50 -f
Restart a site
docker restart lucidia-earth
Update a site
# Build new version locally
npm run build
# Deploy
scp -r dist/* pi@192.168.4.64:/tmp/dist/
ssh pi@192.168.4.64 "docker restart lucidia-earth"
Rollback
docker stop lucidia-earth
docker rename lucidia-earth-old lucidia-earth
docker start lucidia-earth
Caddy Configuration
Location: /etc/caddy/Caddyfile (inside caddy container)
Current routing:
# Main lucidia.earth
lucidia.earth, www.lucidia.earth {
reverse_proxy localhost:3040
}
# BlackRoad main site
blackroad.io {
reverse_proxy localhost:3000
}
# Subdomains
earth.blackroad.io {
reverse_proxy localhost:3013
}
demo.blackroad.io {
reverse_proxy localhost:3002
}
# Quantum domains
blackroadqi.com {
reverse_proxy localhost:3020
}
# Blockchain
roadchain.io {
reverse_proxy localhost:3031
}
roadcoin.io {
reverse_proxy localhost:3030
}
# Future Lucidia subdomains
app.lucidia.earth {
reverse_proxy localhost:3041
}
tube.lucidia.earth {
reverse_proxy localhost:3042
}
studio.lucidia.earth {
reverse_proxy localhost:3043
}
Caddy automatically handles:
- SSL certificates (Let's Encrypt)
- HTTP → HTTPS redirect
- Certificate renewal
- Load balancing (if multiple backends)
Port Allocation
Reserved ports on the Pi:
3000 - blackroad.io (main site)
3001 - node-api-3001
3002 - demo.blackroad.io
3003 - node-api-3003
3004 - creator-studio.blackroad.io
3005 - devops.blackroad.io
3006 - education.blackroad.io
3007 - finance.blackroad.io
3008 - ideas.blackroad.io
3009 - legal.blackroad.io
3010 - research-lab.blackroad.io
3011 - studio.blackroad.io
3012 - brand.blackroad.io
3013 - earth.blackroad.io
3020 - blackroadqi.com
3021 - blackroadquantum.info
3022 - blackroadquantum.net
3023 - blackroadquantum.shop
3024 - blackroadquantum.store
3030 - roadcoin.io
3031 - roadchain.io
3040 - lucidia.earth (3D Metaverse)
3041 - app.lucidia.earth (reserved)
3042 - tube.lucidia.earth (reserved)
3043 - studio.lucidia.earth (reserved)
3044 - (available)
3050 - blackroad-docs
8081 - blackroad-whoami
9000 - portainer
9443 - portainer (https)
9444 - blackroadinc.us
Next available: 3044+
Deployment Automation Script
Save as ~/deploy-to-pi.sh:
#!/usr/bin/env bash
# deploy-to-pi.sh - Deploy any project to the Pi
# Usage: ./deploy-to-pi.sh <project-name> <port> <domain>
set -e
PROJECT=$1
PORT=$2
DOMAIN=$3
if [ -z "$PROJECT" ] || [ -z "$PORT" ] || [ -z "$DOMAIN" ]; then
echo "Usage: ./deploy-to-pi.sh <project-name> <port> <domain>"
echo "Example: ./deploy-to-pi.sh lucidia-metaverse 3040 lucidia.earth"
exit 1
fi
echo "🚀 Deploying $PROJECT to $DOMAIN on port $PORT..."
# Build
echo "📦 Building..."
npm run build
# Package
echo "📦 Packaging..."
tar czf /tmp/$PROJECT.tar.gz dist/
# Upload
echo "📤 Uploading to Pi..."
scp /tmp/$PROJECT.tar.gz pi@192.168.4.64:/tmp/
# Deploy
echo "🐳 Deploying container..."
ssh pi@192.168.4.64 << ENDSSH
cd /tmp
tar xzf $PROJECT.tar.gz
docker stop $PROJECT 2>/dev/null || true
docker rm $PROJECT 2>/dev/null || true
docker run -d \
--name $PROJECT \
--restart unless-stopped \
-p $PORT:80 \
-v /tmp/dist:/usr/share/nginx/html:ro \
nginx:alpine
echo "✅ Container running on port $PORT"
ENDSSH
echo ""
echo "✅ DEPLOYED!"
echo " Container: $PROJECT"
echo " Port: $PORT"
echo " URL: https://$DOMAIN"
echo ""
echo "Next steps:"
echo " 1. Add domain to Caddy config"
echo " 2. Add DNS record in Cloudflare"
rm /tmp/$PROJECT.tar.gz
Usage:
chmod +x ~/deploy-to-pi.sh
~/deploy-to-pi.sh lucidia-metaverse 3040 lucidia.earth
Monitoring
Check Pi status
ssh pi@192.168.4.64 "
echo 'System:'
uptime
echo ''
echo 'Containers:'
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
echo ''
echo 'Caddy status:'
docker logs caddy --tail 5
"
Check tunnel status
ssh pi@192.168.4.64 "ps aux | grep cloudflared"
Check site is live
curl -s https://lucidia.earth | grep -o '<title>.*</title>'
Backups
All container volumes are stored in /var/lib/docker/volumes/ on the Pi.
To backup a site:
ssh pi@192.168.4.64
docker commit lucidia-earth lucidia-earth:backup-$(date +%Y%m%d)
docker save lucidia-earth:backup-$(date +%Y%m%d) | gzip > ~/backups/lucidia-earth-$(date +%Y%m%d).tar.gz
The Truth
This is how we actually deploy everything. Not GitHub Actions. Not Cloudflare Pages. Just:
- Build locally
- SCP to Pi
- Docker run
- Done
30 seconds. Every time. No exceptions.
"The simplest system that works." 🛣️