Add deploy script and update prod compose
Deploy script builds and pushes images to Gitea registry, then triggers Portainer stack redeployment via API. Includes preflight checks for branch and uncommitted changes. Also renames prod DB volume to avoid conflicts with dev and changes frontend port to 9080. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,12 +22,12 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
|
||||
|
||||
**Docker Compose + Portainer + Gitea (source hosting, container registry, CI/CD)**
|
||||
|
||||
1. **Gitea** runs on Unraid behind Nginx Proxy Manager with SSL (e.g., `gitea.yourdomain.com`). It serves as the self-hosted Git remote, container registry, and (optionally) CI/CD via Gitea Actions.
|
||||
2. **Images are built on the dev machine** and pushed to Gitea's container registry as **user-level packages** (e.g., `gitea.yourdomain.com/julian/nuzlocke-tracker-api:latest`, `gitea.yourdomain.com/julian/nuzlocke-tracker-frontend:latest`).
|
||||
1. **Gitea** runs on Unraid behind Nginx Proxy Manager with SSL (e.g., `gitea.nerdboden.de`). It serves as the self-hosted Git remote, container registry, and (optionally) CI/CD via Gitea Actions.
|
||||
2. **Images are built on the dev machine** and pushed to Gitea's container registry as **user-level packages** (e.g., `gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest`, `gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest`).
|
||||
3. **Production runs docker-compose** on Unraid, pulling images from the Gitea container registry instead of mounting source.
|
||||
4. **Portainer** is installed on Unraid to manage stacks, provide a web UI, and enable webhook-triggered redeployments.
|
||||
5. **A deploy script** on the dev machine automates the full flow: build images → push to Gitea registry → trigger Portainer webhook to redeploy.
|
||||
6. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.yourdomain.com` → frontend container, `gitea.yourdomain.com` → Gitea).
|
||||
6. **Nginx Proxy Manager** handles routing on the LAN (e.g., `nuzlocke.nerdboden.de` → frontend container, `gitea.nerdboden.de` → Gitea).
|
||||
7. **Database** uses a named Docker volume for persistence; migrations run automatically on API container startup.
|
||||
|
||||
## Branching Strategy
|
||||
@@ -48,9 +48,9 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p
|
||||
|
||||
- [ ] **Set up branching structure** — create `develop` branch from `main`, establish the `main`/`develop`/`feature/*` workflow
|
||||
- [ ] **Update CLAUDE.md with branching rules** — once the branching structure is in place, add instructions to CLAUDE.md that the branching strategy must be adhered to (always work on feature branches, never commit directly to `main`, merge flow is `feature/*` → `develop` → `main`)
|
||||
- [ ] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.yourdomain.com` works, test pushing and pulling an image as a user-level package
|
||||
- [ ] **Configure Gitea container registry** — create an access token with `read:package` and `write:package` scopes, verify `docker login gitea.nerdboden.de` works, test pushing and pulling an image as a user-level package
|
||||
- [x] **Create production docker-compose file** (`docker-compose.prod.yml`) — uses images from the Gitea container registry, production env vars, no source volume mounts, proper restart policies
|
||||
- [ ] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode
|
||||
- [x] **Create production Dockerfiles (or multi-stage builds)** — ensure frontend is built and served statically (e.g., via the API or a lightweight nginx container), API runs without debug mode
|
||||
- [x] **Set up Portainer on Unraid** — install Portainer CE as a Docker container, configure the stack from the production compose file
|
||||
- [ ] **Configure Portainer webhook for automated redeployment** — add a webhook trigger in Portainer that pulls latest images and restarts the stack
|
||||
- [ ] **Create deploy script** — a script (e.g., `./deploy.sh`) that builds images from `main`, tags them for the Gitea registry, pushes them, and triggers the Portainer webhook to redeploy
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
# nuzlocke-tracker-aiw6
|
||||
title: Create deploy script
|
||||
status: todo
|
||||
status: in-progress
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-02-09T15:30:48Z
|
||||
updated_at: 2026-02-09T15:31:15Z
|
||||
updated_at: 2026-02-09T17:22:53Z
|
||||
parent: nuzlocke-tracker-ahza
|
||||
blocking:
|
||||
- nuzlocke-tracker-izf6
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
---
|
||||
# nuzlocke-tracker-jzqz
|
||||
title: Configure Portainer webhook for automated redeployment
|
||||
status: todo
|
||||
title: Configure Portainer API for automated redeployment
|
||||
status: in-progress
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-02-09T15:30:45Z
|
||||
updated_at: 2026-02-09T15:31:15Z
|
||||
updated_at: 2026-02-09T17:22:17Z
|
||||
parent: nuzlocke-tracker-ahza
|
||||
blocking:
|
||||
- nuzlocke-tracker-hwyk
|
||||
---
|
||||
|
||||
Set up a webhook in Portainer that triggers a stack redeployment when called.
|
||||
Use the Portainer CE REST API to trigger stack redeployments from the deploy script.
|
||||
|
||||
- Create a webhook trigger in Portainer for the nuzlocke-tracker stack
|
||||
- The webhook should pull the latest images from the local registry and restart the stack
|
||||
- Note the webhook URL for use in the deploy script
|
||||
Portainer webhooks are a Business-only feature, so we use the API directly instead.
|
||||
|
||||
## Approach
|
||||
|
||||
1. Authenticate with the Portainer API to get a JWT token
|
||||
2. Call the stack update endpoint with `pullImage: true` to pull latest images and recreate containers
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Identify the stack ID in Portainer (via API or UI)
|
||||
- [ ] Test API authentication (`POST /api/auth`)
|
||||
- [ ] Test triggering a stack redeploy via API
|
||||
- [ ] Integrate into the deploy script
|
||||
81
deploy.sh
Executable file
81
deploy.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration ──────────────────────────────────────────────
|
||||
REGISTRY="gitea.nerdboden.de"
|
||||
OWNER="thefurya"
|
||||
IMAGES=("nuzlocke-tracker-api" "nuzlocke-tracker-frontend")
|
||||
DOCKERFILES=("backend/Dockerfile.prod" "frontend/Dockerfile.prod")
|
||||
CONTEXTS=("./backend" "./frontend")
|
||||
|
||||
PORTAINER_URL="${PORTAINER_URL:-https://portainer.nerdboden.de}"
|
||||
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"
|
||||
PORTAINER_STACK_ID="${PORTAINER_STACK_ID:-}"
|
||||
PORTAINER_ENDPOINT_ID="${PORTAINER_ENDPOINT_ID:-1}"
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
|
||||
|
||||
# ── Preflight checks ──────────────────────────────────────────
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [[ "$BRANCH" != "main" ]]; then
|
||||
warn "You are on branch '$BRANCH', not 'main'."
|
||||
read -rp "Continue anyway? [y/N] " confirm
|
||||
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
|
||||
fi
|
||||
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
warn "You have uncommitted changes."
|
||||
read -rp "Continue anyway? [y/N] " confirm
|
||||
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
|
||||
fi
|
||||
|
||||
# ── Build and push images ─────────────────────────────────────
|
||||
for i in "${!IMAGES[@]}"; do
|
||||
IMAGE="${REGISTRY}/${OWNER}/${IMAGES[$i]}:latest"
|
||||
info "Building ${IMAGES[$i]}..."
|
||||
docker build -t "$IMAGE" -f "${DOCKERFILES[$i]}" "${CONTEXTS[$i]}"
|
||||
info "Pushing ${IMAGES[$i]}..."
|
||||
docker push "$IMAGE"
|
||||
done
|
||||
|
||||
info "All images built and pushed."
|
||||
|
||||
# ── Trigger Portainer redeployment ─────────────────────────────
|
||||
if [[ -z "$PORTAINER_API_KEY" ]]; then
|
||||
warn "PORTAINER_API_KEY not set — skipping Portainer redeployment."
|
||||
warn "Set it in your environment or .env.deploy file to enable auto-redeploy."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$PORTAINER_STACK_ID" ]]; then
|
||||
warn "PORTAINER_STACK_ID not set — skipping Portainer redeployment."
|
||||
warn "Find your stack ID in Portainer and set it in your environment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Fetching stack file from Portainer..."
|
||||
STACK_FILE=$(curl -sf \
|
||||
-H "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
"${PORTAINER_URL}/api/stacks/${PORTAINER_STACK_ID}/file") \
|
||||
|| error "Failed to fetch stack file from Portainer."
|
||||
|
||||
STACK_CONTENT=$(echo "$STACK_FILE" | jq -r '.StackFileContent')
|
||||
|
||||
info "Triggering stack redeployment..."
|
||||
curl -sf -X PUT \
|
||||
-H "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg content "$STACK_CONTENT" '{"pullImage": true, "stackFileContent": $content}')" \
|
||||
"${PORTAINER_URL}/api/stacks/${PORTAINER_STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}" \
|
||||
> /dev/null \
|
||||
|| error "Failed to trigger Portainer redeployment."
|
||||
|
||||
info "Stack redeployment triggered successfully!"
|
||||
@@ -1,9 +1,6 @@
|
||||
services:
|
||||
api:
|
||||
image: gitea.nerdboden.de/julian/nuzlocke-tracker-api:latest
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.prod
|
||||
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-api:latest
|
||||
command: >
|
||||
sh -c "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000 --app-dir src"
|
||||
environment:
|
||||
@@ -15,12 +12,9 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
image: gitea.nerdboden.de/julian/nuzlocke-tracker-frontend:latest
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.prod
|
||||
image: gitea.nerdboden.de/thefurya/nuzlocke-tracker-frontend:latest
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "9080:80"
|
||||
depends_on:
|
||||
- api
|
||||
restart: unless-stopped
|
||||
@@ -32,7 +26,7 @@ services:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=nuzlocke
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- prod_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
@@ -41,4 +35,4 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
prod_postgres_data:
|
||||
|
||||
Reference in New Issue
Block a user