Skip to content

Deploy Docs

Deploy Docs #165

Workflow file for this run

name: Deploy Docs
on:
workflow_run:
workflows: ["Docs"]
types: [completed]
workflow_dispatch:
inputs:
image_tag:
description: "Docker image tag to deploy"
required: true
default: "latest"
concurrency:
group: deploy-docs
cancel-in-progress: false
env:
HEALTH_URL: https://doc-claude.brewcode.app/getting-started/
permissions:
contents: read
deployments: write
jobs:
deploy:
runs-on: ubuntu-latest
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
fetch-depth: 0
- name: Compute image tag
id: tag
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "value=${{ inputs.image_tag }}" >> "$GITHUB_OUTPUT"
else
REF="${{ github.event.workflow_run.head_branch || github.ref_name }}"
if [[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "value=${REF#v}" >> "$GITHUB_OUTPUT"
else
BRANCH_SAFE=$(echo "$REF" | sed 's/[^a-zA-Z0-9._-]/-/g')
DESC=$(git describe --tags --long --match "v*.*.*" 2>/dev/null || echo "0.0.0-0-g$(git rev-parse --short HEAD)")
BASE_VERSION=$(echo "$DESC" | sed 's/^v//' | sed 's/-.*//')
COMMITS_AFTER=$(echo "$DESC" | sed 's/.*-\([0-9]*\)-g.*/\1/')
echo "value=${BASE_VERSION}-${BRANCH_SAFE}-${COMMITS_AFTER}" >> "$GITHUB_OUTPUT"
fi
fi
- name: Create deployment
id: deployment
uses: actions/github-script@v8
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'docs',
auto_merge: false,
required_contexts: [],
description: `Deploy docs v${{ steps.tag.outputs.value }}`,
});
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
state: 'in_progress',
log_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});
return deployment.data.id;
- name: Copy deploy files to VPS
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: "web/docs/deploy/docker-compose.yml,web/docs/deploy/docs.Caddyfile,web/docs/deploy/brewcode-docs.caddy"
target: /tmp/brewcode-docs-sync
strip_components: 3
- name: Deploy docs service
uses: appleboy/ssh-action@v1
env:
TAG: ${{ steps.tag.outputs.value }}
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
envs: TAG
script: |
set -euo pipefail
DOCS_PATH=/opt/brewcode-docs
CADDY_SITES=/opt/caddy/sites
cp /tmp/brewcode-docs-sync/docker-compose.yml "$DOCS_PATH/"
cp /tmp/brewcode-docs-sync/docs.Caddyfile "$DOCS_PATH/"
cp /tmp/brewcode-docs-sync/brewcode-docs.caddy "$CADDY_SITES/"
rm -rf /tmp/brewcode-docs-sync
cd "$DOCS_PATH"
[ -f .env ] && cp .env .env.bak || touch .env.bak
if grep -q "^DOCS_TAG=" .env 2>/dev/null; then
sed -i "s/^DOCS_TAG=.*/DOCS_TAG=${TAG}/" .env
else
echo "DOCS_TAG=${TAG}" >> .env
fi
for attempt in $(seq 1 10); do
if docker compose pull docs 2>&1; then
break
fi
if [ "$attempt" -eq 10 ]; then
echo "Failed to pull image after 10 attempts"
exit 1
fi
echo "Image not available yet, retrying in 15s... (attempt $attempt/10)"
sleep 15
done
docker compose up -d --no-deps --force-recreate docs
# Reload main caddy to pick up the docs site snippet.
# admin is bound 0.0.0.0:2019 with origins {caddy:2019, localhost:2019};
# a plain `caddy reload` derives Origin http://0.0.0.0:2019 (the config's admin
# address), which is NOT in that allowlist -> HTTP 403. Connect via localhost:2019,
# which IS allowed, so the graceful reload succeeds.
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --address localhost:2019
for i in $(seq 1 10); do
if curl -sf -o /dev/null "https://doc-claude.brewcode.app/getting-started/"; then
echo "Health check passed (attempt $i)"
exit 0
fi
echo "Waiting for docs... (attempt $i/10)"
sleep 5
done
echo "Health check failed, rolling back..."
cp .env.bak .env
docker compose pull docs
docker compose up -d --no-deps --force-recreate docs
exit 1
- name: Verify from runner
run: |
for i in $(seq 1 5); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" || true)
if [[ "$STATUS" == "200" ]]; then
echo "External health check passed (HTTP $STATUS)"
exit 0
fi
echo "Waiting... (attempt $i/5, HTTP $STATUS)"
sleep 5
done
echo "::warning::External health check did not return 200"
- name: Update deployment (success)
if: success()
uses: actions/github-script@v8
with:
script: |
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deployment.outputs.result }},
state: 'success',
environment_url: 'https://doc-claude.brewcode.app',
log_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});
- name: Update deployment (failure)
if: failure()
uses: actions/github-script@v8
with:
script: |
const id = ${{ steps.deployment.outputs.result || 0 }};
if (!id) return;
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: id,
state: 'failure',
log_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});
- name: Summary
if: always()
run: |
echo "### Deploy Docs" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Tag:** \`${{ steps.tag.outputs.value }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "**Health:** $HEALTH_URL" >> "$GITHUB_STEP_SUMMARY"