Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/actions/cobalt-deploy/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: 'Cobalt deploy (blue/green)'
description: >-
Trigger a zero-downtime Cobalt deployment of a prebuilt public image tag and
wait until it is live. Cobalt pulls the image, starts the new deployment,
HTTP-probes it from inside Caddy, then swaps the route atomically — the old
version keeps serving until the new one is healthy.
inputs:
cobalt-url:
description: 'Cobalt daemon base URL'
required: true
cobalt-api-key:
description: 'Cobalt API key'
required: true
project:
description: 'Cobalt project name'
required: true
image:
description: 'Prebuilt public image ref to deploy'
required: true
port:
description: 'Container port Caddy probes before cutover'
required: true
runs:
using: 'composite'
steps:
- shell: bash
env:
COBALT_URL: ${{ inputs.cobalt-url }}
COBALT_API_KEY: ${{ inputs.cobalt-api-key }}
PROJECT: ${{ inputs.project }}
IMAGE: ${{ inputs.image }}
PORT: ${{ inputs.port }}
run: |
set -euo pipefail

# The image tag is the source of truth for the release; the rest of the
# cobaltfile (domains, env, volumes) lives in the cobalt-ops repo and is
# reconciled by Cobalt. Cobalt HTTP-probes the web service port from
# inside Caddy before the atomic route swap, so no health command needed.
cobaltfile=$(jq -nc \
--arg project "$PROJECT" \
--arg image "$IMAGE" \
--argjson port "$PORT" \
'{version:"1.0",name:$project,services:{web:{type:"container",image:$image,port:$port}}}')

body=$(jq -nc --arg cf "$cobaltfile" '{cobaltfileOverride:$cf}')

echo "Deploying $PROJECT -> $IMAGE (port $PORT)"
resp=$(curl -fsS -X POST "$COBALT_URL/api/projects/$PROJECT/deployments" \
-H "Authorization: Bearer $COBALT_API_KEY" \
-H 'Content-Type: application/json' \
-d "$body")

id=$(printf '%s' "$resp" | jq -r '.id')
echo "enqueued deployment id=$id number=$(printf '%s' "$resp" | jq -r '.number')"

status=""
for _ in $(seq 1 60); do
status=$(curl -fsS "$COBALT_URL/api/deployments/$id" \
-H "Authorization: Bearer $COBALT_API_KEY" | jq -r '.status')
echo " $PROJECT deployment status: $status"
case "$status" in
success)
echo "✅ $PROJECT is live"
exit 0
;;
failed|canceled|skipped)
echo "❌ $PROJECT deploy ended with '$status'"
curl -fsS --max-time 25 "$COBALT_URL/api/deployments/$id/output" \
-H "Authorization: Bearer $COBALT_API_KEY" | tail -80 || true
exit 1
;;
esac
sleep 5
done

echo "❌ $PROJECT deploy timed out (last status: ${status:-unknown})" >&2
exit 1
6 changes: 6 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ on:
required: false
default: true
type: boolean
build_args:
description: 'Docker build args (KEY=VALUE, one per line)'
required: false
default: ''
type: string
outputs:
image:
description: 'Fully qualified image reference that was published'
Expand Down Expand Up @@ -81,6 +86,7 @@ jobs:
file: ${{ inputs.dockerfile }}
platforms: ${{ matrix.platform }}
provenance: false
build-args: ${{ inputs.build_args }}
cache-from: type=gha,scope=${{ inputs.slug }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ inputs.slug }}-${{ matrix.arch }}
outputs: type=image,name=${{ inputs.image }},push-by-digest=true,name-canonical=true,push=true
Expand Down
Loading
Loading