From 56111291c7e974c534c2d389cb977146bd1cc5fb Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Thu, 15 Oct 2026 03:23:55 +0400 Subject: [PATCH] ci(deploy): deploy published images via SSH and Docker Compose --- .env.example | 3 + .gitea/workflows/ci.yml | 125 +++++++++++++++++++++++++++++++++++++++- compose.yaml | 11 ++-- 3 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b047b7c --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +API_URL=https://api.vacationplanner.ru +UPSTREAM_TIMEOUT_SECONDS=15 +HTTP_PORT=8080 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 1d312db..c49dfba 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -9,7 +9,7 @@ on: concurrency: group: ci-${{ gitea.ref }} - cancel-in-progress: true + cancel-in-progress: false permissions: contents: read @@ -19,6 +19,8 @@ jobs: name: Test and publish runs-on: ubuntu-latest timeout-minutes: 30 + outputs: + image: ${{ steps.publish.outputs.image }} env: IMAGE: code.popov.link/valentineus/vacationplanner2ics TEST_IMAGE: vacationplanner2ics-test:ci-${{ gitea.run_id }} @@ -75,6 +77,7 @@ jobs: test "$(docker inspect --format '{{.State.ExitCode}}' "$SMOKE_CONTAINER")" = 0 - name: Publish to Gitea Container Registry + id: publish if: >- gitea.event_name != 'pull_request' && (gitea.ref == 'refs/heads/master' || startsWith(gitea.ref, 'refs/tags/v')) @@ -96,10 +99,130 @@ jobs: TAG="${REF#refs/tags/v}" fi docker tag "$RUNTIME_IMAGE" "$IMAGE:$TAG" + docker tag "$RUNTIME_IMAGE" "$IMAGE:sha-$REVISION" + docker push "$IMAGE:sha-$REVISION" docker push "$IMAGE:$TAG" + printf 'image=%s:sha-%s\n' "$IMAGE" "$REVISION" >> "$GITHUB_OUTPUT" - name: Remove temporary containers and image tags if: always() run: | docker rm -f "$SMOKE_CONTAINER" 2>/dev/null || true docker image rm "$TEST_IMAGE" "$RUNTIME_IMAGE" 2>/dev/null || true + + deploy-server: + name: Deploy published image to production + needs: ci + if: >- + gitea.ref == 'refs/heads/master' && + (gitea.event_name == 'push' || gitea.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + IMAGE: ${{ needs.ci.outputs.image }} + DEPLOY_SSH_HOST: ${{ secrets.DEPLOY_SSH_HOST }} + DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT }} + DEPLOY_SSH_USER: ${{ secrets.DEPLOY_SSH_USER }} + DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} + DEPLOY_SSH_PRIVATE_KEY_BASE64: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY_BASE64 }} + DEPLOY_SSH_KNOWN_HOSTS: ${{ secrets.DEPLOY_SSH_KNOWN_HOSTS }} + REGISTRY_PULL_USERNAME: ${{ secrets.REGISTRY_PULL_USERNAME }} + REGISTRY_PULL_TOKEN: ${{ secrets.REGISTRY_PULL_TOKEN }} + steps: + - name: Checkout deployment files + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Configure pinned SSH + shell: bash + run: | + set -Eeuo pipefail + set +x + umask 077 + mkdir -p "$HOME/.ssh" + printf '%s' "$DEPLOY_SSH_PRIVATE_KEY_BASE64" | base64 --decode > "$HOME/.ssh/vacationplanner-deploy" + printf '%s\n' "$DEPLOY_SSH_KNOWN_HOSTS" > "$HOME/.ssh/vacationplanner-deploy_known_hosts" + cat > "$HOME/.ssh/vacationplanner-deploy_config" <> .env + " + + - name: Start service + shell: bash + run: | + ssh -F "$HOME/.ssh/vacationplanner-deploy_config" vacationplanner-deploy " + set -e + cd '$DEPLOY_PATH' + docker compose up --pull never --detach --force-recreate app + " + + - name: Check readiness + shell: bash + run: | + ssh -F "$HOME/.ssh/vacationplanner-deploy_config" vacationplanner-deploy " + set -eu + cd '$DEPLOY_PATH' + container_id=\$(docker compose ps --quiet app) + bind_addr=\$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' \"\$container_id\" | sed -n 's/^BIND_ADDR=//p') + curl --fail --silent --show-error --retry 15 --retry-all-errors \ + --retry-delay 2 --max-time 3 \"http://\$bind_addr/healthz\" | grep -qx ok + " + + - name: Cleanup SSH + if: always() + shell: bash + run: | + rm -f -- "$HOME/.ssh/vacationplanner-deploy" \ + "$HOME/.ssh/vacationplanner-deploy_known_hosts" \ + "$HOME/.ssh/vacationplanner-deploy_config" diff --git a/compose.yaml b/compose.yaml index 15e06c3..3ba12f1 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,13 +1,14 @@ +name: vacationplanner2ics + services: app: - build: - context: . - target: runtime + image: ${IMAGE:-code.popov.link/valentineus/vacationplanner2ics:latest} + pull_policy: always + network_mode: host environment: + BIND_ADDR: 127.0.0.1:${HTTP_PORT:-8080} API_URL: ${API_URL:-https://api.vacationplanner.ru} UPSTREAM_TIMEOUT_SECONDS: ${UPSTREAM_TIMEOUT_SECONDS:-15} - ports: - - "127.0.0.1:8080:8080" restart: unless-stopped read_only: true cap_drop: [ALL]