Hostityourself/docs/setup.md
2026-03-24 14:27:12 +01:00

9.2 KiB

HostItYourself — Raspberry Pi Setup Guide

End-to-end guide to turn a fresh Raspberry Pi into a self-hosted PaaS. After completing this guide you will have:

  • Automatic HTTPS for every deployed app via Caddy + Cloudflare
  • git push → live deploys triggered by GitHub webhooks
  • A web dashboard at hiy.yourdomain.com
  • Firewall, fail2ban, and SSH key-only hardening

Hardware

Component Minimum Recommended
Pi model Raspberry Pi 4 4 GB Raspberry Pi 4/5 8 GB
Storage 32 GB microSD (A2) 64 GB+ USB SSD
Network Wi-Fi Wired Ethernet
Power Official USB-C PSU PSU + UPS hat

A USB SSD is strongly preferred — builds involve a lot of small file I/O that kills microSD cards within months.


1. Install Raspberry Pi OS

  1. Download Raspberry Pi Imager and flash Raspberry Pi OS Lite (64-bit).
  2. Before writing, click the gear icon (⚙) and:
    • Enable SSH with a public-key (paste your ~/.ssh/id_ed25519.pub)
    • Set a hostname, e.g. hiypi
    • Set your Wi-Fi credentials (or leave blank for wired)
  3. Insert the SD/SSD and boot the Pi.

Verify you can SSH in:

ssh pi@hiypi.local

2. First boot — update and configure

sudo apt update && sudo apt full-upgrade -y
sudo apt install -y git curl ufw fail2ban unattended-upgrades

Edit /etc/dhcpcd.conf and add at the bottom (adjust for your network):

interface eth0
static ip_address=192.168.1.50/24
static routers=192.168.1.1
static domain_name_servers=1.1.1.1 8.8.8.8
sudo reboot

3. Harden SSH

Disable password authentication so only your key can log in:

sudo nano /etc/ssh/sshd_config

Set / verify:

PasswordAuthentication no
PermitRootLogin no
sudo systemctl restart ssh

Test that you can still log in with your key before closing the current session.


4. Firewall

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP (redirect)
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable
sudo ufw status

fail2ban

The default config already jails repeated SSH failures. Restart to apply:

sudo systemctl enable fail2ban --now

5. Install Docker

Note: Do not run apt install docker-ce directly — that package requires Docker's own apt repository to be added first. Use one of the two methods below.

Option A — Official convenience script (recommended, installs latest Docker CE):

curl -fsSL https://get.docker.com | sh

Option B — Debian-packaged docker.io (older but simpler, no extra repo needed):

sudo apt install -y docker.io docker-compose

Note: docker.io ships with Compose v1 as a separate binary. Use docker-compose (with a hyphen) instead of docker compose everywhere in this guide.

Then, regardless of which option you used:

sudo usermod -aG docker pi   # allow running docker without sudo
newgrp docker                # apply group without logging out
docker run --rm hello-world  # verify

6. Domain and DNS

You need a domain pointing to your Pi's public IP. Any DNS provider works.

  1. Add DNS A records pointing to your public home IP:

    Type Name Content
    A hiy <your-public-IP>
    A * <your-public-IP>

    The wildcard * record routes <appname>.yourdomain.com to the Pi.

Port forwarding

Forward the following ports on your router to the Pi's static IP. Both ports are required — Caddy uses port 80 to prove domain ownership to Let's Encrypt before issuing the HTTPS certificate.

External Internal
80/tcp 80
443/tcp 443

7. Clone the platform

git clone https://github.com/shautvast/Hostityourself.git ~/hiy-platform
cd ~/hiy-platform

8. Configure the platform

Copy and edit the environment file:

cp .env.example .env   # if it doesn't exist, create it
nano .env

Minimum required variables:

# Your domain (apps will be at <name>.yourdomain.com)
DOMAIN_SUFFIX=yourdomain.com

# Email for Let's Encrypt expiry notices
ACME_EMAIL=you@example.com

9. Caddy — automatic HTTPS

Caddy obtains a Let's Encrypt certificate automatically via the HTTP-01 challenge. No DNS API token or Cloudflare account required.

The proxy/Caddyfile is already configured — nothing to edit here. Just make sure DOMAIN_SUFFIX and ACME_EMAIL are set in infra/.env and that ports 80 and 443 are forwarded to the Pi (see step 6).


10. Start the platform

cd ~/hiy-platform
./infra/start.sh

Open the dashboard: https://yourdomain.com


11. Deploy your first app

From the dashboard

  1. Open https://hiy.yourdomain.com
  2. Fill in the Add App form:
    • Name — a URL-safe slug, e.g. my-api
    • GitHub Repo URLhttps://github.com/you/my-api.git
    • Branchmain
    • Container Port — the port your app listens on (e.g. 3000)
  3. Click Create App, then Deploy on the new row.
  4. Watch the build log in the app detail page.
  5. Once done, your app is live at https://my-api.yourdomain.com.

Build strategy detection

The build engine picks a strategy automatically:

What's in the repo Strategy
Dockerfile docker build
package.json / yarn.lock Cloud Native Buildpack (Node)
requirements.txt / pyproject.toml Cloud Native Buildpack (Python)
go.mod Cloud Native Buildpack (Go)
static/ or public/ directory Caddy static file server

A Dockerfile always takes precedence. For buildpack strategies, the pack CLI must be installed on the Pi (see below).

Install pack (for buildpack projects)

curl -sSL https://github.com/buildpacks/pack/releases/latest/download/pack-linux-arm64.tgz \
  | sudo tar -xz -C /usr/local/bin
pack --version

12. Set up GitHub webhooks (auto-deploy on push)

For each app:

  1. Go to the app detail page → GitHub Webhook section. Copy the Payload URL and Secret.

  2. Open the GitHub repo → Settings → Webhooks → Add webhook:

    | Field | Value | |---|---| | Payload URL | https://hiy.yourdomain.com/webhook/<app-id> | | Content type | application/json | | Secret | (from the app detail page) | | Events | Just the push event |

  3. Click Add webhook. GitHub will send a ping — check Recent Deliveries for a green tick.

After this, every git push to the configured branch triggers a deploy.


13. Backups

Create a daily backup cron job:

sudo nano /etc/cron.daily/hiy-backup
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/var/backups/hiy
DATE=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"

# Database
docker exec hiy-platform-server-1 sh -c \
  'sqlite3 /data/hiy.db .dump' > "$BACKUP_DIR/db-$DATE.sql"

# Env files
tar -czf "$BACKUP_DIR/env-$DATE.tar.gz" \
  -C /var/lib/docker/volumes/hiy-platform_hiy-data/_data env/ 2>/dev/null || true

# Keep 30 days
find "$BACKUP_DIR" -mtime +30 -delete

echo "HIY backup complete: $BACKUP_DIR"
sudo chmod +x /etc/cron.daily/hiy-backup

To copy backups off-Pi, add an rclone copy line pointing to S3, Backblaze B2, or any rclone remote.


14. Monitoring (optional)

Netdata — per-container resource metrics

curl https://my-netdata.io/kickstart.sh | bash

Access at http://hiypi.local:19999 or add a Caddy route for netdata.yourdomain.com.

Gatus — HTTP uptime checks

Create ~/gatus/config.yaml:

endpoints:
  - name: my-api
    url: https://my-api.yourdomain.com/health
    interval: 1m
    conditions:
      - "[STATUS] == 200"
    alerts:
      - type: email
        description: "my-api is down"
docker run -d --name gatus \
  -p 8080:8080 \
  -v ~/gatus:/config \
  twinproduction/gatus

15. Updating the platform itself

cd ~/hiy-platform
git pull origin main
docker compose --env-file infra/.env up -d --build

Zero-downtime: the old containers keep serving traffic while the new ones build. Caddy never restarts.


Troubleshooting

Symptom Likely cause Fix
Dashboard not reachable Compose not started / port 443 not forwarded docker compose logs server
TLS certificate error Port 80/443 not forwarded or DNS not propagated yet Check Caddy logs: docker compose logs caddy
Build fails — "docker not found" Docker socket not mounted Verify docker-proxy service is up
Build fails — "pack not found" pack not installed See step 11
Webhook returns 401 Secret mismatch Regenerate app, re-copy secret to GitHub
Webhook returns 404 Wrong app ID in URL Check app ID on the dashboard
App runs but 502 from browser Container port wrong Check Container Port matches what the app listens on
Container shows "exited" App crashed on startup docker logs hiy-<app-id>