diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml
index 8e8266ec..4c4668c6 100644
--- a/.github/workflows/deploy-site.yml
+++ b/.github/workflows/deploy-site.yml
@@ -1,4 +1,4 @@
-name: Deploy static site to GitHub Pages
+name: Deploy static site
on:
push:
@@ -20,7 +20,8 @@ concurrency:
cancel-in-progress: false
jobs:
- deploy:
+ deploy-github-pages:
+ name: Deploy to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
@@ -40,4 +41,30 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
\ No newline at end of file
+ uses: actions/deploy-pages@v4
+
+ deploy-server:
+ name: Deploy to hatch.surf server
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Deploy via rsync
+ uses: burnett01/rsync-deployments@7.0.1
+ with:
+ switches: -avz --delete
+ path: site/
+ remote_path: /var/www/hatch.surf/
+ remote_host: ${{ secrets.DEPLOY_HOST }}
+ remote_user: ${{ secrets.DEPLOY_USER }}
+ remote_key: ${{ secrets.DEPLOY_KEY }}
+
+ - name: Reload nginx
+ uses: appleboy/ssh-action@v1
+ with:
+ host: ${{ secrets.DEPLOY_HOST }}
+ username: ${{ secrets.DEPLOY_USER }}
+ key: ${{ secrets.DEPLOY_KEY }}
+ script: |
+ nginx -t && systemctl reload nginx
diff --git a/docs/engineering/deploy-homepage.md b/docs/engineering/deploy-homepage.md
new file mode 100644
index 00000000..16265619
--- /dev/null
+++ b/docs/engineering/deploy-homepage.md
@@ -0,0 +1,170 @@
+# Deploying the Hatch Homepage
+
+This document describes how the Hatch static homepage is deployed to hatch.surf.
+
+## Overview
+
+The homepage is deployed to two locations:
+
+1. **GitHub Pages** - Serves as a fallback and for GitHub-based discovery
+2. **hatch.surf server** - Primary deployment at https://hatch.surf
+
+## Architecture
+
+```
+site/
+├── index.html # Main homepage
+├── style.css # Stylesheet
+├── brand/ # Logo and favicon assets
+│ ├── favicon/
+│ └── og/
+└── blog/ # Blog directory (placeholder)
+```
+
+## Server Setup
+
+### Prerequisites
+
+- Server: 46.250.250.48 (hatch.surf)
+- nginx installed and configured
+- Let's Encrypt certificate for hatch.surf
+- SSH access for deployment
+
+### Nginx Configuration
+
+The nginx server block is located at:
+- `/etc/nginx/sites-available/hatch.surf.conf`
+- Symlinked to `/etc/nginx/sites-enabled/hatch.surf.conf`
+
+Configuration highlights:
+- HTTP → HTTPS redirect
+- SSL with Let's Encrypt certificate
+- Security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy)
+- Gzip compression
+- Static asset caching (30 days)
+
+### SSL Certificate
+
+- Certificate managed by Let's Encrypt
+- Auto-renewal via certbot
+- Expiry: September 22, 2026
+
+## Deployment
+
+### Automated (GitHub Actions)
+
+When changes are pushed to `main` branch affecting `site/` directory:
+
+1. **GitHub Pages** - Deployed automatically via GitHub Actions
+2. **Server** - Deployed via rsync to `/var/www/hatch.surf/`
+
+Required GitHub secrets:
+- `DEPLOY_HOST` - Server IP (46.250.250.48)
+- `DEPLOY_USER` - SSH username (root)
+- `DEPLOY_KEY` - SSH private key (base64 encoded)
+
+### Manual Deployment
+
+Use the deployment script:
+
+```bash
+# Dry run
+./scripts/deploy-site.sh --dry-run
+
+# Deploy
+DEPLOY_HOST=46.250.250.48 \
+DEPLOY_USER=root \
+DEPLOY_KEY=$(cat ~/.ssh/id_rsa | base64) \
+./scripts/deploy-site.sh
+```
+
+### Direct rsync
+
+```bash
+rsync -avz --delete site/ root@46.250.250.48:/var/www/hatch.surf/
+ssh root@46.250.250.48 "nginx -t && systemctl reload nginx"
+```
+
+## Verification
+
+### Check Site Status
+
+```bash
+# HTTP redirect
+curl -s -o /dev/null -w "%{http_code}" http://hatch.surf/
+
+# HTTPS site
+curl -s -o /dev/null -w "%{http_code}" https://hatch.surf/
+
+# SSL certificate
+curl -v https://hatch.surf/ 2>&1 | grep -E "expire|issuer|subject"
+
+# Static assets
+curl -s -o /dev/null -w "%{http_code}" https://hatch.surf/style.css
+curl -s -o /dev/null -w "%{http_code}" https://hatch.surf/brand/og/default.png
+```
+
+### Browser Verification
+
+1. Visit https://hatch.surf
+2. Verify no browser warnings (SSL valid)
+3. Check that all assets load (CSS, images)
+4. Test responsive design
+
+## Troubleshooting
+
+### SSL Certificate Issues
+
+```bash
+# Check certificate status
+sudo certbot certificates
+
+# Renew certificate manually
+sudo certbot renew --cert-name hatch.surf
+
+# Test nginx config
+sudo nginx -t
+```
+
+### Deployment Failures
+
+1. Check GitHub Actions logs
+2. Verify SSH access: `ssh root@46.250.250.48`
+3. Check nginx status: `sudo systemctl status nginx`
+4. Check nginx logs: `sudo tail -f /var/log/nginx/error.log`
+
+### Missing Assets
+
+1. Verify files exist on server: `ls -la /var/www/hatch.surf/brand/`
+2. Check nginx access logs: `sudo tail -f /var/log/nginx/access.log`
+3. Ensure proper file permissions: `chown -R www-data:www-data /var/www/hatch.surf`
+
+## Maintenance
+
+### Updating Content
+
+1. Edit files in `site/` directory
+2. Commit and push to `main` branch
+3. GitHub Actions will auto-deploy to both locations
+
+### SSL Renewal
+
+Certbot is configured to auto-renew. To check renewal status:
+
+```bash
+sudo certbot renew --dry-run
+```
+
+### Backup
+
+The site is version-controlled in Git. For server backups:
+
+```bash
+# Backup nginx config
+sudo tar -czf nginx-hatch-backup.tar.gz /etc/nginx/sites-available/hatch.surf.conf /etc/letsencrypt/live/hatch.surf/
+```
+
+## Related Issues
+
+- [ELF-192](/ELF/issues/ELF-192) - Build homepage and deploy it on hatch.surf
+- [ELF-171](/ELF/issues/ELF-171) - Server setup (nginx, SSL)
diff --git a/scripts/deploy-site.sh b/scripts/deploy-site.sh
new file mode 100755
index 00000000..24d1e66d
--- /dev/null
+++ b/scripts/deploy-site.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+# Deploy static site to hatch.surf server
+# Usage: ./scripts/deploy-site.sh [--dry-run]
+#
+# Environment variables required:
+# DEPLOY_HOST - SSH host (e.g., 46.250.250.48)
+# DEPLOY_USER - SSH user (e.g., root)
+# DEPLOY_KEY - SSH private key (base64 encoded, optional if using ssh-agent)
+#
+# The script deploys the contents of the site/ directory to /var/www/hatch.surf
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+SITE_DIR="$REPO_ROOT/site"
+REMOTE_DIR="/var/www/hatch.surf"
+
+# Configuration
+DEPLOY_HOST="${DEPLOY_HOST:-46.250.250.48}"
+DEPLOY_USER="${DEPLOY_USER:-root}"
+DEPLOY_KEY="${DEPLOY_KEY:-}"
+
+# Parse arguments
+DRY_RUN=false
+for arg in "$@"; do
+ case $arg in
+ --dry-run)
+ DRY_RUN=true
+ ;;
+ esac
+done
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+log() {
+ echo -e "${GREEN}✓${NC} $1"
+}
+
+warn() {
+ echo -e "${YELLOW}⚠${NC} $1"
+}
+
+error() {
+ echo -e "${RED}✗${NC} $1" >&2
+ exit 1
+}
+
+# Validate
+if [ ! -d "$SITE_DIR" ]; then
+ error "Site directory not found: $SITE_DIR"
+fi
+
+if [ ! -f "$SITE_DIR/index.html" ]; then
+ error "index.html not found in $SITE_DIR"
+fi
+
+# Setup SSH key if provided
+SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
+if [ -n "$DEPLOY_KEY" ]; then
+ SSH_KEY_FILE=$(mktemp)
+ echo "$DEPLOY_KEY" | base64 -d > "$SSH_KEY_FILE"
+ chmod 600 "$SSH_KEY_FILE"
+ SSH_OPTS="$SSH_OPTS -i $SSH_KEY_FILE"
+fi
+
+# Deploy
+DEPLOY_TARGET="${DEPLOY_USER}@${DEPLOY_HOST}"
+
+if [ "$DRY_RUN" = true ]; then
+ warn "Dry run - would deploy to $DEPLOY_TARGET:$REMOTE_DIR"
+ rsync -avz --delete --dry-run $SSH_OPTS "$SITE_DIR/" "$DEPLOY_TARGET:$REMOTE_DIR/"
+else
+ log "Deploying to $DEPLOY_TARGET:$REMOTE_DIR"
+ rsync -avz --delete $SSH_OPTS "$SITE_DIR/" "$DEPLOY_TARGET:$REMOTE_DIR/"
+
+ # Reload nginx
+ log "Reloading nginx..."
+ ssh $SSH_OPTS "$DEPLOY_TARGET" "nginx -t && systemctl reload nginx"
+
+ log "Deployment complete!"
+ log "Site live at https://hatch.surf"
+fi
+
+# Cleanup SSH key if created
+if [ -n "$SSH_KEY_FILE" ]; then
+ rm -f "$SSH_KEY_FILE"
+fi
diff --git a/site/index.html b/site/index.html
index 9ca45652..97d91eba 100644
--- a/site/index.html
+++ b/site/index.html
@@ -20,7 +20,7 @@
-
+