Files
blackroad-domains/PI_DEPLOYMENT_REAL.md
Alexa Louise 9e54848b5a Add REAL deployment docs - Pi direct deployment
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>
2025-12-22 19:21:57 -06:00

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:

  1. Build locally
  2. SCP to Pi
  3. Docker run
  4. Done

30 seconds. Every time. No exceptions.


"The simplest system that works." 🛣️