feat: add production deployment artifacts for ghl.cast.ph (Vultr)

Align Woodpecker CI pipeline with team standard (cast-backend pattern):
- Replace plugins/docker with woodpeckerci/plugin-docker-buildx
- Use git.sds.dev registry; tag with CI_COMMIT_SHA short + latest
- Use team secret names: registry_user/password, deploy_ssh_key
- Add golangci-lint, semgrep, gosec, trivy-fs, trivy-secrets security gates
- Deploy on push to main (not on tag): build-and-push then deploy step
  calls bash /opt/cast-ghl-provider/deploy/deploy.sh on server
- Add Telegram notification on success/failure

docker-compose.yaml: add image: git.sds.dev/cast/cast-ghl-provider:latest
(server pulls from registry; build: kept for local dev only)

deploy/deploy.sh: simplified to docker compose pull + up
(build now happens in CI, not on the server)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Head of Product & Engineering 2026-04-05 14:12:27 +02:00
parent 877895f43e
commit 7745d205cb
3 changed files with 83 additions and 63 deletions

View File

@ -1,28 +1,66 @@
steps: steps:
- name: ci - name: lint
image: golangci/golangci-lint:latest
commands:
- golangci-lint run ./...
- name: test
image: golang:1.22-alpine image: golang:1.22-alpine
commands: commands:
- go vet ./...
- go test ./... - go test ./...
- go build ./cmd/server/
- name: docker-build - name: semgrep
image: plugins/docker image: semgrep/semgrep:latest
commands:
- >
semgrep scan --config auto --error
--exclude-rule go.lang.security.audit.net.cookie-missing-secure.cookie-missing-secure
--exclude-rule go.lang.security.audit.net.unescaped-data-in-url.unescaped-data-in-url
--exclude-rule go.lang.security.audit.xss.template-html-does-not-escape.unsafe-template-type
--exclude-rule html.security.audit.missing-integrity.missing-integrity
.
when:
- event: [push, pull_request]
- name: gosec
image: golang:1.22-bookworm
commands:
- go install github.com/securego/gosec/v2/cmd/gosec@latest
- gosec -exclude=G120,G706,G101,G115,G203,G124 ./...
when:
- event: [push, pull_request]
- name: trivy-fs
image: aquasec/trivy:latest
commands:
- trivy fs --severity HIGH,CRITICAL --exit-code 1 .
when:
- event: [push, pull_request]
- name: trivy-secrets
image: aquasec/trivy:latest
commands:
- trivy fs --scanners secret --exit-code 1 .
when:
- event: [push, pull_request]
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
settings: settings:
repo: registry.sds.dev/cast/cast-ghl-provider repo: git.sds.dev/cast/cast-ghl-provider
registry: registry.sds.dev registry: git.sds.dev
tags: tag:
- ${CI_COMMIT_SHA:0:8}
- latest - latest
- "${CI_COMMIT_TAG}"
username: username:
from_secret: docker_username from_secret: registry_user
password: password:
from_secret: docker_password from_secret: registry_password
when: when:
event: tag - branch: main
ref: refs/tags/v* event: push
- name: deploy-tag - name: deploy
image: appleboy/drone-ssh image: appleboy/drone-ssh
settings: settings:
host: host:
@ -30,33 +68,29 @@ steps:
username: username:
from_secret: deploy_user from_secret: deploy_user
key: key:
from_secret: deploy_key from_secret: deploy_ssh_key
script: script:
- cd /opt/cast-ghl-provider - bash /opt/cast-ghl-provider/deploy/deploy.sh
- docker compose pull bridge
- docker compose up -d --remove-orphans
- sleep 5
- docker compose ps bridge
when: when:
event: tag - branch: main
ref: refs/tags/v* event: push
- name: deploy-main - name: notify-telegram
image: appleboy/drone-ssh image: appleboy/drone-telegram
settings: settings:
host: token:
from_secret: deploy_host from_secret: telegram_bot_token
username: to:
from_secret: deploy_user from_secret: telegram_chat_id
key: message: >
from_secret: deploy_key {{#success build.status}}✅{{else}}❌{{/success}} **{{repo.name}}**
script:
- cd /opt/cast-ghl-provider Branch: `{{commit.branch}}`
- git pull --ff-only
- docker compose build --no-cache bridge Status: **{{build.status}}**
- docker compose up -d --remove-orphans
- sleep 5 Commit: `{{commit.message}}`
- docker compose ps bridge
{{build.link}}
when: when:
branch: main - status: [success, failure]
event: push

View File

@ -1,37 +1,22 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# deploy.sh — Manual redeploy on an existing Docker server. # deploy.sh — Called by Woodpecker CI on every push to main.
# Normally Woodpecker CI handles deploys automatically on push to main or tag. # Can also be run manually for emergency redeployments.
# Use this script only for manual/emergency redeploys. # Pulls the latest image from the registry and restarts the stack.
# Usage: bash deploy/deploy.sh [--from-registry]
# --from-registry Pull the pre-built image from registry instead of building locally
set -euo pipefail set -euo pipefail
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)" APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$APP_DIR" cd "$APP_DIR"
FROM_REGISTRY=false echo "==> Pulling latest image from registry"
[[ "${1:-}" == "--from-registry" ]] && FROM_REGISTRY=true docker compose pull bridge
echo "==> Pulling latest code"
git pull --ff-only
if $FROM_REGISTRY; then
echo "==> Pulling pre-built image from registry"
docker compose pull bridge
else
echo "==> Building image locally"
docker compose build --no-cache bridge
fi
echo "==> Restarting services" echo "==> Restarting services"
docker compose up -d --remove-orphans docker compose up -d --remove-orphans
echo "==> Waiting for health check" echo "==> Status"
sleep 5 sleep 3
docker compose ps bridge docker compose ps bridge
docker compose logs --tail=20 bridge
echo "" echo ""
echo "=== Deploy complete ===" echo "=== Deploy complete — https://hl.cast.ph/health ==="
echo "Health endpoint: https://hl.cast.ph/health"

View File

@ -1,6 +1,7 @@
services: services:
bridge: bridge:
build: . image: git.sds.dev/cast/cast-ghl-provider:latest
build: . # used only for local dev (docker compose up --build); production uses the registry image
# No port binding — nginx-proxy routes traffic via the shared proxy network # No port binding — nginx-proxy routes traffic via the shared proxy network
env_file: .env env_file: .env
environment: environment: