diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b9aaaa7..a85489e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -124,28 +124,31 @@ jobs: runs-on: ubuntu-latest needs: build-and-push if: github.event_name != 'pull_request' + # Single-host deploy: the runner lives on the same box as the stack, so we + # drive the host docker daemon directly (act_runner shares its socket via + # `docker_host: "-"`) instead of SSHing out. The compose dir is bind-mounted + # at its REAL host path so compose's relative bind-mounts (./mangalord/..., + # ./Caddyfile) resolve; this requires `/mnt/ssd/docker-data` in the runner's + # container.valid_volumes. The central compose references the images as + # registry.mc02.dev/mangalord-*:${MANGALORD_TAG:-latest}, so we only pull + # and recreate the two mangalord services at the freshly built SHA. + container: + image: docker:cli + volumes: + - /mnt/ssd/docker-data:/mnt/ssd/docker-data steps: - - name: SSH deploy - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USER }} - key: ${{ secrets.SSH_PRIVATE_KEY }} - port: ${{ secrets.SSH_PORT || 22 }} - envs: REGISTRY_URL,REGISTRY_USERNAME,REGISTRY_PASSWORD,IMAGE_TAG,DEPLOY_PATH - script_stop: true - script: | - set -euo pipefail - cd "$DEPLOY_PATH" - echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin - export REGISTRY_URL IMAGE_TAG - docker compose -f docker-compose.yml -f docker-compose.prod.yml pull - docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d - docker image prune -f - docker logout "$REGISTRY_URL" + - name: Deploy to the local stack + working-directory: /mnt/ssd/docker-data env: REGISTRY_URL: ${{ secrets.REGISTRY_URL }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }} - DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} + run: | + set -eu + echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin + export MANGALORD_TAG="$IMAGE_TAG" + docker compose pull mangalord-backend mangalord-frontend + docker compose up -d mangalord-backend mangalord-frontend + docker image prune -f + docker logout "$REGISTRY_URL" diff --git a/backend/Dockerfile b/backend/Dockerfile index a61154f..065269e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,7 +10,8 @@ RUN apt-get update \ # exact crate versions CI tested. Without Cargo.lock + the flag, cargo # would silently resolve fresh on every image build. COPY Cargo.toml Cargo.lock ./ -RUN mkdir src && echo "fn main() {}" > src/main.rs && echo "" > src/lib.rs \ +RUN mkdir -p src/bin && echo "fn main() {}" > src/main.rs && echo "" > src/lib.rs \ + && echo "fn main() {}" > src/bin/crawler.rs \ && cargo build --locked --release \ && rm -rf src @@ -18,7 +19,10 @@ COPY src ./src COPY migrations ./migrations RUN touch src/main.rs src/lib.rs && cargo build --locked --release -FROM debian:bookworm-slim +FROM debian:trixie-slim +# Runtime base must match the builder's Debian release: `rust:1-slim` tracks +# trixie (glibc 2.41), so a bookworm runtime (glibc 2.36) can't run the +# binary ("GLIBC_2.39 not found"). Keep these two in lockstep on bumps. # `curl` is for the container HEALTHCHECK; `ca-certificates` is for # outbound HTTPS (crawler covers/pages). # diff --git a/frontend/Dockerfile b/frontend/Dockerfile index dcca067..33f3824 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -24,10 +24,12 @@ COPY --from=builder --chown=node:node /app/package.json ./ USER node EXPOSE 3000 -# Alpine's busybox `wget` is the canonical lightweight HTTP probe. -# `--spider` doesn't follow redirects; `node build` serves a 200 on -# `/` for the homepage so this works without a dedicated /health. +# Alpine's busybox `wget` is the canonical lightweight HTTP probe. Probe +# 127.0.0.1, not `localhost`: musl resolves `localhost` to IPv6 ::1 first, +# but the Node server binds IPv4 0.0.0.0 only, so a localhost probe gets +# "connection refused" and the container is wrongly marked unhealthy. Use a +# GET (`-O /dev/null`) since `node build` serves 200 on `/`. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD wget -q --spider http://localhost:3000/ || exit 1 + CMD wget -q -O /dev/null http://127.0.0.1:3000/ || exit 1 CMD ["node", "build"]