From 48a3d3f3f521344b69702d8d01176d9bc7768035 Mon Sep 17 00:00:00 2001 From: rlabadmin Date: Thu, 23 Apr 2026 14:21:51 +0200 Subject: [PATCH] Initial scaffold: static site served by nginx on :8080 - public/index.html served via nginx.conf (port 8080) - Dockerfile: nginx:1.27-alpine + HEALTHCHECK - .gitea/workflows/flow.yml: build + push to Gitea registry, rolling deploy - stack.yml: Swarm service wired to Traefik (host testwebsite.dev.serso.org) Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 7 +++ .gitea/workflows/flow.yml | 90 +++++++++++++++++++++++++++++++++++++++ Dockerfile | 9 ++++ nginx.conf | 17 ++++++++ public/index.html | 29 +++++++++++++ stack.yml | 33 ++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/flow.yml create mode 100644 Dockerfile create mode 100644 nginx.conf create mode 100644 public/index.html create mode 100644 stack.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..057619d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.gitea +.gitignore +.dockerignore +stack.yml +README.md +*.md diff --git a/.gitea/workflows/flow.yml b/.gitea/workflows/flow.yml new file mode 100644 index 0000000..5430f8a --- /dev/null +++ b/.gitea/workflows/flow.yml @@ -0,0 +1,90 @@ +# .gitea/workflows/flow.yml +# ============================================================= +# serso — generic CI/CD flow +# Copy into any app repo as .gitea/workflows/flow.yml +# Adjust the env block (STACK_NAME, SERVICE_NAME, APP_DOMAIN) per app +# ============================================================= + +name: Build & Deploy + +on: + push: + branches: + - main + workflow_dispatch: + +env: + # ─── Registry ───────────────────────────────────────────── + REGISTRY: git.dev.serso.org + IMAGE: ${{ github.repository }} # → owner/reponame + + # ─── Per-app knobs (edit these) ─────────────────────────── + STACK_NAME: testwebsite # Swarm stack name + SERVICE_NAME: testwebsite_web # {stack}_{service} + APP_DOMAIN: testwebsite.dev.serso.org # used only for logging + +jobs: + + # ============================================================ + # 1. Build image + push to Gitea registry + # ============================================================ + build: + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.meta.outputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Compute tag + id: meta + run: echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT + + - name: Log in to Gitea registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | \ + docker login ${{ env.REGISTRY }} \ + --username ${{ secrets.REGISTRY_USER }} \ + --password-stdin + + - name: Build image + run: | + docker build \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.meta.outputs.tag }} \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \ + . + + - name: Push image + run: | + docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.meta.outputs.tag }} + docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest + + # ============================================================ + # 2. Deploy to Swarm (rolling update) + # ============================================================ + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Prepare SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + + - name: Rolling deploy on Swarm + run: | + ssh -i ~/.ssh/deploy_key \ + ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \ + "docker service update \ + --image ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ needs.build.outputs.image_tag }} \ + --with-registry-auth \ + --update-order start-first \ + --update-failure-action rollback \ + ${{ env.SERVICE_NAME }}" + + - name: Deployed 🎉 + run: | + echo "App deployed at https://${{ env.APP_DOMAIN }}" + echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ needs.build.outputs.image_tag }}" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..39a4cec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM nginx:1.27-alpine + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY public/ /usr/share/nginx/html/ + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ + CMD wget -q -O - http://127.0.0.1:8080/ >/dev/null || exit 1 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..4d318ea --- /dev/null +++ b/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } + + location = /healthz { + access_log off; + add_header Content-Type text/plain; + return 200 "ok\n"; + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..2e1590d --- /dev/null +++ b/public/index.html @@ -0,0 +1,29 @@ + + + + + + testsite + + + +

testsite is live

+

Served by nginx on port 8080, deployed by Gitea Actions to Docker Swarm.

+

Build: dev

+ + diff --git a/stack.yml b/stack.yml new file mode 100644 index 0000000..209bb39 --- /dev/null +++ b/stack.yml @@ -0,0 +1,33 @@ +# stack.yml — generic Swarm deploy file +# Deploy once: docker stack deploy -c stack.yml myapp +# After that, CI updates the image via `docker service update` — no redeploy needed + +version: "3.9" + +networks: + traefik-public: + external: true + +services: + web: + image: git.dev.serso.org/test/testsite:latest + networks: + - traefik-public + deploy: + replicas: 2 + placement: + constraints: + - node.role == worker # run on node01/node02, not cp + update_config: + parallelism: 1 + order: start-first # zero-downtime (spin new up, then down old) + failure_action: rollback + restart_policy: + condition: on-failure + labels: + - "traefik.enable=true" + - "traefik.http.routers.testwebsite.rule=Host(`testwebsite.dev.serso.org`)" + - "traefik.http.routers.testwebsite.entrypoints=websecure" + - "traefik.http.routers.testwebsite.tls.certresolver=letsencrypt" + # Port that your app listens on INSIDE the container + - "traefik.http.services.testwebsite.loadbalancer.server.port=8080"