docs: add auto-update watcher and deploy scripts for nginx-proxy setup

Hee Sung Son committed Mar 9, 2026 at 18:51 UTC 3e77f77788a4daf788bca7a3198f47c569e6f51c
4 files changed +139 -2
docs/deployment.md
+83 -2
@@ -165,9 +165,90 @@ If your proxy source addresses are public or you want a stricter allowlist, also
165 docker compose up
166 ```
167
168 -## 5. Troubleshooting
168 +## 5. Auto-Update
169
170 -### 5.1 Ports blocked
170 +Automatically redeploy when a new `ghcr.io/gosuda/portal:latest` image is pushed.
171 +
172 +### 5.1 Deploy script
173 +
174 +Create `deploy_portal.sh` in your project directory:
175 +
176 +```bash
177 +#!/usr/bin/env bash
178 +set -euo pipefail
179 +
180 +cd "$(dirname "$0")"
181 +
182 +docker compose pull
183 +docker compose up -d
184 +docker image prune -f
185 +```
186 +
187 +### 5.2 Watcher script
188 +
189 +The repository includes `watch_and_deploy.sh`, which polls the remote image digest and runs the deploy script on change.
190 +
191 +Environment variables:
192 +
193 +| Variable | Default | Description |
194 +|---|---|---|
195 +| `INTERVAL` | `60` | Poll interval in seconds |
196 +| `DEPLOY_SCRIPT` | `deploy_portal.sh` | Path to deploy script |
197 +| `DIGEST_FILE` | `.portal_image_digest` | File storing the last known digest |
198 +
199 +### 5.3 Register as systemd service
200 +
201 +Set `WorkingDirectory` and `ExecStart` to the directory where `watch_and_deploy.sh` and `deploy_portal.sh` are located:
202 +
203 +```bash
204 +sudo tee /etc/systemd/system/portal-watcher.service << 'EOF'
205 +[Unit]
206 +Description=Portal Docker Image Watcher
207 +After=network-online.target docker.service
208 +Wants=network-online.target
209 +Requires=docker.service
210 +
211 +[Service]
212 +Type=simple
213 +User=opc
214 +# Set to the directory containing watch_and_deploy.sh and deploy_portal.sh
215 +WorkingDirectory=<path-to-project>
216 +ExecStart=/bin/bash <path-to-project>/watch_and_deploy.sh
217 +Restart=always
218 +RestartSec=10
219 +Environment=INTERVAL=60
220 +Environment=DEPLOY_SCRIPT=deploy_portal.sh
221 +
222 +[Install]
223 +WantedBy=multi-user.target
224 +EOF
225 +
226 +sudo systemctl daemon-reload
227 +sudo systemctl enable --now portal-watcher
228 +```
229 +
230 +Adjust `User` to match your environment. Ensure the user belongs to the `docker` group:
231 +
232 +```bash
233 +sudo usermod -aG docker opc
234 +```
235 +
236 +### 5.4 Verify and monitor
237 +
238 +```bash
239 +# Service status
240 +sudo systemctl status portal-watcher
241 +
242 +# Live logs
243 +sudo journalctl -u portal-watcher -f
244 +
245 +# Today's logs only
246 +sudo journalctl -u portal-watcher --since today
247 +```
248 +
249 +## 6. Troubleshooting
250 +
251 +### 6.1 Ports blocked
252
253 Required inbound ports:
254
docs/examples/nginx-proxy/deploy_portal.sh new
+7
@@ -0,0 +1,7 @@
1 +#!/bin/bash
2 +set -e
3 +
4 +docker pull ghcr.io/gosuda/portal:latest
5 +docker compose down portal
6 +docker compose up -d portal
7 +bash nginx_deploy.sh
docs/examples/nginx-proxy/nginx_deploy.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +set -e
3 +
4 +docker cp nginx:/run/nginx.pid ./nginx.pid
5 +docker cp ./nginx.conf nginx:/etc/nginx/nginx.conf
6 +docker exec nginx nginx -s reload
7 +
8 +docker cp ./nginx.pid nginx:/run/nginx.pid
9 +rm ./nginx.pid
docs/examples/nginx-proxy/watch_and_deploy.sh new
+40
@@ -0,0 +1,40 @@
1 +#!/usr/bin/env bash
2 +set -euo pipefail
3 +
4 +IMAGE="ghcr.io/gosuda/portal:latest"
5 +DIGEST_FILE="${DIGEST_FILE:-.portal_image_digest}"
6 +INTERVAL="${INTERVAL:-60}"
7 +DEPLOY_SCRIPT="${DEPLOY_SCRIPT:-deploy_portal.sh}"
8 +
9 +get_remote_digest() {
10 + docker manifest inspect "$IMAGE" 2>/dev/null \
11 + | grep -m1 '"digest"' \
12 + | awk -F'"' '{print $4}'
13 +}
14 +
15 +echo "Watching $IMAGE for digest changes (interval: ${INTERVAL}s)"
16 +echo "Deploy script: $DEPLOY_SCRIPT"
17 +
18 +while true; do
19 + NEW_DIGEST=$(get_remote_digest)
20 +
21 + if [[ -z "$NEW_DIGEST" ]]; then
22 + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Failed to fetch digest, retrying in ${INTERVAL}s"
23 + sleep "$INTERVAL"
24 + continue
25 + fi
26 +
27 + OLD_DIGEST=""
28 + if [[ -f "$DIGEST_FILE" ]]; then
29 + OLD_DIGEST=$(cat "$DIGEST_FILE")
30 + fi
31 +
32 + if [[ "$NEW_DIGEST" != "$OLD_DIGEST" ]]; then
33 + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Digest changed: ${OLD_DIGEST:-<none>} -> $NEW_DIGEST"
34 + echo "$NEW_DIGEST" > "$DIGEST_FILE"
35 + bash "$DEPLOY_SCRIPT"
36 + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Deploy completed"
37 + fi
38 +
39 + sleep "$INTERVAL"
40 +done