|
| 1 | +# Deployment Guide |
| 2 | + |
| 3 | +This repository is served as a static site from a Docker container on a cloud server (e.g., GCP), accessible at `dev.cppdigest.org/boost/`. GitHub Pages remains active solely to redirect legacy visitors to the new URL. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +``` |
| 8 | +Push to main |
| 9 | + ├── deploy.yml → SSH into server → git pull /opt/boost → nginx container serves updated files |
| 10 | + └── pages.yml → (only if index.html or 404.html changed) → GitHub Pages serves redirect-only artifact |
| 11 | +``` |
| 12 | + |
| 13 | +The site content lives in a git clone at `/opt/boost` on the server. An nginx Docker container bind-mounts that directory read-only, so a `git pull` is all that is needed to deploy content changes — no Docker image rebuild, no container restart. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Repository Files |
| 18 | + |
| 19 | +| File | Purpose | |
| 20 | +|---|---| |
| 21 | +| `Dockerfile` | Builds the nginx:alpine container image with a custom nginx config | |
| 22 | +| `nginx.conf` | Container-internal nginx config — serves files from the bind-mounted directory | |
| 23 | +| `docker-compose.yml` | Defines the `boost-site` container, port mapping, and bind mount | |
| 24 | +| `.github/workflows/deploy.yml` | CD workflow — SSHs into the server and runs `git pull` on every push to `main` | |
| 25 | +| `.github/workflows/pages.yml` | GitHub Pages workflow — deploys redirect-only artifact when `index.html` or `404.html` change | |
| 26 | +| `index.html` | Root page — contains a hostname-conditional redirect for GitHub Pages visitors | |
| 27 | +| `404.html` | GitHub Pages 404 handler — redirects deep-linked legacy URLs to their GCP equivalents | |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Part 1 — GitHub Repository Configuration |
| 32 | + |
| 33 | +### 1.1 Add Repository Secrets |
| 34 | + |
| 35 | +Go to **Settings → Secrets and variables → Actions → New repository secret** and add: |
| 36 | + |
| 37 | +| Secret name | Value | |
| 38 | +|---|---| |
| 39 | +| `SERVER_HOST` | IP address or hostname of your cloud server | |
| 40 | +| `SERVER_USER` | SSH username on the server (e.g. `ubuntu`) | |
| 41 | +| `SERVER_SSH_KEY` | Full contents of the private SSH key (the matching public key must be in `~/.ssh/authorized_keys` on the server) | |
| 42 | + |
| 43 | +### 1.2 Switch GitHub Pages Source to GitHub Actions |
| 44 | + |
| 45 | +Go to **Settings → Pages → Build and deployment → Source** and change from **"Deploy from a branch"** to **"GitHub Actions"**. |
| 46 | + |
| 47 | +This is required for `pages.yml` to deploy. Without it the workflow will fail with a permissions error. |
| 48 | + |
| 49 | +> After this change, GitHub Pages will only update when the `pages.yml` workflow runs — which only happens when `index.html` or `404.html` are modified. Routine content pushes (e.g. updating the `develop/` directory) will not trigger a Pages redeploy. |
| 50 | +
|
| 51 | +--- |
| 52 | + |
| 53 | +## Part 2 — Server Setup (one-time) |
| 54 | + |
| 55 | +SSH into your server and run the following commands once to prepare the environment. |
| 56 | + |
| 57 | +### 2.1 Install Docker and Docker Compose |
| 58 | + |
| 59 | +If not already installed: |
| 60 | + |
| 61 | +```bash |
| 62 | +curl -fsSL https://get.docker.com | sh |
| 63 | +sudo usermod -aG docker $USER |
| 64 | +newgrp docker |
| 65 | +``` |
| 66 | + |
| 67 | +Verify: |
| 68 | + |
| 69 | +```bash |
| 70 | +docker --version |
| 71 | +docker compose version |
| 72 | +``` |
| 73 | + |
| 74 | +### 2.2 Clone the Repository |
| 75 | + |
| 76 | +```bash |
| 77 | +sudo git clone https://github.com/CppDigest/cppdigest.github.io.git /opt/boost |
| 78 | +``` |
| 79 | + |
| 80 | +### 2.3 Set Ownership and Permissions |
| 81 | + |
| 82 | +Transfer ownership to your SSH user so that `git pull` works without `sudo`, and set world-readable permissions so the nginx container user (UID 101 inside `nginx:alpine`) can read the files via the bind mount: |
| 83 | + |
| 84 | +```bash |
| 85 | +sudo chown -R $USER:$USER /opt/boost |
| 86 | +find /opt/boost -type d -exec chmod 755 {} \; |
| 87 | +find /opt/boost -type f -exec chmod 644 {} \; |
| 88 | +``` |
| 89 | + |
| 90 | +**Why this works:** The `nginx:alpine` worker process runs as the `nginx` user (UID 101). The bind mount is owned by your SSH user, so nginx reads the files as "other". Standard `755` (directories) and `644` (files) permissions grant world-read access. The deploy workflow's `git pull` runs as your SSH user (the owner), so no `sudo` is needed there either. |
| 91 | + |
| 92 | +### 2.4 Build and Start the Container |
| 93 | + |
| 94 | +```bash |
| 95 | +cd /opt/boost |
| 96 | +docker compose up -d --build |
| 97 | +``` |
| 98 | + |
| 99 | +### 2.5 Verify the Container is Serving |
| 100 | + |
| 101 | +```bash |
| 102 | +curl http://localhost:9102/ |
| 103 | +``` |
| 104 | + |
| 105 | +You should receive the HTML content of `index.html`. If you see an nginx error page, check `docker logs boost-site`. |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## Part 3 — Host nginx Configuration (one-time) |
| 110 | + |
| 111 | +The server runs a host-level nginx instance that routes subdirectory requests to individual Docker containers. Add a `location` block for `/boost/` inside the existing `server` block for `dev.cppdigest.org`. |
| 112 | + |
| 113 | +```nginx |
| 114 | +location /boost/ { |
| 115 | + proxy_pass http://127.0.0.1:9102/; |
| 116 | + proxy_set_header Host $host; |
| 117 | + proxy_set_header X-Real-IP $remote_addr; |
| 118 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +The trailing slash on `proxy_pass` causes nginx to strip the `/boost/` prefix before forwarding requests to the container. All internal HTML links in this repo use relative paths, so no URL rewriting is needed. |
| 123 | + |
| 124 | +Test and reload: |
| 125 | + |
| 126 | +```bash |
| 127 | +sudo nginx -t && sudo systemctl reload nginx |
| 128 | +``` |
| 129 | + |
| 130 | +Verify end-to-end from your local machine: |
| 131 | + |
| 132 | +```bash |
| 133 | +curl https://dev.cppdigest.org/boost/ |
| 134 | +``` |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +## Part 4 — Continuous Deployment |
| 139 | + |
| 140 | +After the one-time setup above, all future deployments are automatic. |
| 141 | + |
| 142 | +### How deploy.yml works |
| 143 | + |
| 144 | +On every push to `main` (or manual dispatch), the workflow SSHs into the server and runs: |
| 145 | + |
| 146 | +```bash |
| 147 | +cd /opt/boost |
| 148 | +git fetch origin main |
| 149 | +CHANGED=$(git diff HEAD origin/main --name-only) |
| 150 | +git pull origin main |
| 151 | +if echo "$CHANGED" | grep -qE "^(Dockerfile|nginx\.conf|docker-compose\.yml)$"; then |
| 152 | + docker compose up -d --build |
| 153 | +fi |
| 154 | +``` |
| 155 | + |
| 156 | +- **Content changes** (HTML files, JSON data): `git pull` updates files on disk; the container's bind mount reflects changes immediately. No restart needed. |
| 157 | +- **Infrastructure changes** (`Dockerfile`, `nginx.conf`, `docker-compose.yml`): the container is rebuilt and restarted automatically. |
| 158 | + |
| 159 | +### How pages.yml works |
| 160 | + |
| 161 | +Triggers only when `index.html` or `404.html` are modified. It copies those two files into a temporary `_pages/` directory and uploads that as the GitHub Pages artifact. The full site content (hundreds of HTML files and large JSON data) is never pushed to GitHub Pages. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Part 5 — GitHub Pages Redirect Behaviour |
| 166 | + |
| 167 | +### index.html redirect |
| 168 | + |
| 169 | +A small script near the top of `<head>` in `index.html` fires only when the page is served from `cppdigest.github.io`: |
| 170 | + |
| 171 | +```html |
| 172 | +<script> |
| 173 | + if (window.location.hostname === 'cppdigest.github.io') { |
| 174 | + window.location.replace('https://dev.cppdigest.org/boost/'); |
| 175 | + } |
| 176 | +</script> |
| 177 | +``` |
| 178 | + |
| 179 | +When served from `dev.cppdigest.org`, the hostname check fails silently and the full page content displays normally. The same file is used for both deployments. |
| 180 | + |
| 181 | +### 404.html deep-link forwarding |
| 182 | + |
| 183 | +GitHub Pages serves `404.html` for any path not present in the Pages artifact (which only contains `index.html` and `404.html`). This handles bookmarked deep links such as `cppdigest.github.io/v1.90/asio.html` by reconstructing the equivalent GCP URL: |
| 184 | + |
| 185 | +```javascript |
| 186 | +window.location.replace( |
| 187 | + 'https://dev.cppdigest.org/boost' + window.location.pathname |
| 188 | +); |
| 189 | +``` |
| 190 | + |
| 191 | +For example: |
| 192 | +- `cppdigest.github.io/v1.90/` → `dev.cppdigest.org/boost/v1.90/` |
| 193 | +- `cppdigest.github.io/v1.90/libraries/asio.html` → `dev.cppdigest.org/boost/v1.90/libraries/asio.html` |
| 194 | + |
| 195 | +--- |
| 196 | + |
| 197 | +## Part 6 — Validation Checklist |
| 198 | + |
| 199 | +After completing all steps above, run through this checklist: |
| 200 | + |
| 201 | +1. Push a content-only change to `main` (e.g. edit a file in `develop/`) |
| 202 | + - `deploy.yml` should trigger and complete in ~10s |
| 203 | + - `pages.yml` should **not** trigger (confirm in the Actions tab) |
| 204 | +2. Browse to `https://dev.cppdigest.org/boost/` and confirm the change is live |
| 205 | +3. Push a change to `index.html` |
| 206 | + - Both `deploy.yml` and `pages.yml` should trigger |
| 207 | +4. Visit `https://cppdigest.github.io/` and confirm it redirects to `https://dev.cppdigest.org/boost/` |
| 208 | +5. Visit `https://cppdigest.github.io/v1.90/` and confirm it redirects to `https://dev.cppdigest.org/boost/v1.90/` |
| 209 | +6. Test manual dispatch: go to **Actions → Deploy to GCP → Run workflow** |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Troubleshooting |
| 214 | + |
| 215 | +**Container is not serving files** |
| 216 | + |
| 217 | +```bash |
| 218 | +docker logs boost-site |
| 219 | +docker compose ps |
| 220 | +``` |
| 221 | + |
| 222 | +Check that `/opt/boost` is world-readable: |
| 223 | +```bash |
| 224 | +ls -la /opt/boost |
| 225 | +``` |
| 226 | + |
| 227 | +**deploy.yml fails with "Permission denied" on git pull** |
| 228 | + |
| 229 | +Ensure ownership was transferred in step 2.3: |
| 230 | +```bash |
| 231 | +ls -la /opt/ |
| 232 | +sudo chown -R $USER:$USER /opt/boost |
| 233 | +``` |
| 234 | + |
| 235 | +**pages.yml fails with "Resource not accessible by integration"** |
| 236 | + |
| 237 | +The GitHub Pages source has not been switched to GitHub Actions. See step 1.2. |
| 238 | + |
| 239 | +**nginx returns 502 Bad Gateway for /boost/** |
| 240 | + |
| 241 | +The Docker container is not running or is not bound to port 9102: |
| 242 | +```bash |
| 243 | +docker compose ps |
| 244 | +curl http://localhost:9102/ |
| 245 | +``` |
| 246 | + |
| 247 | +**Host nginx config change not taking effect** |
| 248 | + |
| 249 | +```bash |
| 250 | +sudo nginx -t # check for syntax errors |
| 251 | +sudo systemctl reload nginx |
| 252 | +``` |
0 commit comments