ci: move deploy pipeline to registry images
This commit is contained in:
@@ -2,6 +2,9 @@ POSTGRES_DB=crank
|
||||
POSTGRES_USER=crank
|
||||
POSTGRES_PASSWORD=change-me
|
||||
CRANK_DATABASE_URL=postgres://crank:change-me@postgres:5432/crank
|
||||
CRANK_ADMIN_API_IMAGE=crank/admin-api:dev
|
||||
CRANK_MCP_SERVER_IMAGE=crank/mcp-server:dev
|
||||
CRANK_UI_IMAGE=crank/ui:dev
|
||||
CRANK_STORAGE_ROOT=/var/lib/crank/storage
|
||||
CRANK_PUBLISH_HOST=127.0.0.1
|
||||
CRANK_ADMIN_BIND=0.0.0.0:3001
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
run: docker build -f apps/ui/Dockerfile .
|
||||
|
||||
deployment:
|
||||
name: Deployment Artifacts
|
||||
name: Deployment Manifests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -84,6 +84,3 @@ jobs:
|
||||
|
||||
- name: Validate docker compose
|
||||
run: docker compose config -q
|
||||
|
||||
- name: Build deployable images
|
||||
run: docker compose build admin-api mcp-server ui
|
||||
|
||||
@@ -12,8 +12,77 @@ concurrency:
|
||||
group: deploy-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
env:
|
||||
IMAGE_TAG: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
|
||||
ADMIN_API_IMAGE: ghcr.io/${{ github.repository_owner }}/crank-admin-api
|
||||
MCP_SERVER_IMAGE: ghcr.io/${{ github.repository_owner }}/crank-mcp-server
|
||||
UI_IMAGE: ghcr.io/${{ github.repository_owner }}/crank-ui
|
||||
|
||||
jobs:
|
||||
build-images:
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.head_branch == 'main'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: admin-api
|
||||
dockerfile: apps/admin-api/Dockerfile
|
||||
- name: mcp-server
|
||||
dockerfile: apps/mcp-server/Dockerfile
|
||||
- name: ui
|
||||
dockerfile: apps/ui/Dockerfile
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Resolve image name
|
||||
id: image
|
||||
run: |
|
||||
case "${{ matrix.name }}" in
|
||||
admin-api) echo "name=${ADMIN_API_IMAGE}" >> "$GITHUB_OUTPUT" ;;
|
||||
mcp-server) echo "name=${MCP_SERVER_IMAGE}" >> "$GITHUB_OUTPUT" ;;
|
||||
ui) echo "name=${UI_IMAGE}" >> "$GITHUB_OUTPUT" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push ${{ matrix.name }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.image.outputs.name }}:${{ env.IMAGE_TAG }}
|
||||
${{ steps.image.outputs.name }}:main
|
||||
cache-from: type=gha,scope=${{ matrix.name }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
|
||||
|
||||
deploy:
|
||||
needs: build-images
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
@@ -41,28 +110,32 @@ jobs:
|
||||
printf '%s\n' "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
|
||||
chmod 644 ~/.ssh/known_hosts
|
||||
|
||||
- name: Sync project to server
|
||||
- name: Sync deployment files to server
|
||||
run: |
|
||||
rsync -az --delete \
|
||||
--exclude ".git" \
|
||||
--exclude "target" \
|
||||
--exclude "notes" \
|
||||
--exclude "node_modules" \
|
||||
./ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/"
|
||||
ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
|
||||
"mkdir -p '${{ secrets.DEPLOY_PATH }}'"
|
||||
rsync -az docker-compose.yml \
|
||||
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/docker-compose.yml"
|
||||
|
||||
- name: Write environment file
|
||||
run: |
|
||||
ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
|
||||
"mkdir -p '${{ secrets.DEPLOY_PATH }}' && cat > '${{ secrets.DEPLOY_PATH }}/.env'" \
|
||||
<<< "${{ secrets.DEPLOY_ENV_FILE }}"
|
||||
{
|
||||
printf '%s\n' "${{ secrets.DEPLOY_ENV_FILE }}"
|
||||
printf 'CRANK_ADMIN_API_IMAGE=%s:%s\n' "${{ env.ADMIN_API_IMAGE }}" "${{ env.IMAGE_TAG }}"
|
||||
printf 'CRANK_MCP_SERVER_IMAGE=%s:%s\n' "${{ env.MCP_SERVER_IMAGE }}" "${{ env.IMAGE_TAG }}"
|
||||
printf 'CRANK_UI_IMAGE=%s:%s\n' "${{ env.UI_IMAGE }}" "${{ env.IMAGE_TAG }}"
|
||||
} | ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
|
||||
"mkdir -p '${{ secrets.DEPLOY_PATH }}' && cat > '${{ secrets.DEPLOY_PATH }}/.env'"
|
||||
|
||||
- name: Deploy with Docker Compose
|
||||
run: |
|
||||
ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" "
|
||||
set -e
|
||||
cd '${{ secrets.DEPLOY_PATH }}'
|
||||
echo '${{ secrets.DEPLOY_REGISTRY_TOKEN }}' | docker login ghcr.io -u '${{ secrets.DEPLOY_REGISTRY_USER }}' --password-stdin
|
||||
docker compose config -q
|
||||
docker compose up -d --build --remove-orphans
|
||||
docker compose pull admin-api mcp-server ui
|
||||
docker compose up -d --remove-orphans
|
||||
"
|
||||
|
||||
- name: Verify health endpoints
|
||||
|
||||
@@ -75,8 +75,8 @@ Crank - платформа для публикации внешних API в в
|
||||
|
||||
В репозитории настроены:
|
||||
|
||||
- `CI` для Rust, UI container и deployment artifacts;
|
||||
- `CD`, который запускается после успешного `CI` на `main` или вручную;
|
||||
- `CI` для Rust, UI и deployment manifests;
|
||||
- `CD`, который после успешного `CI` на `main` собирает versioned images, пушит их в `GHCR` и деплоит через `docker compose pull`;
|
||||
- containerized deployment через `docker compose`.
|
||||
|
||||
## Поддерживаемые протоколы
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
## Current
|
||||
|
||||
### `feat/workspace-access-polish`
|
||||
### `feat/cd-image-pipeline`
|
||||
|
||||
Status: in_progress
|
||||
|
||||
DoD:
|
||||
- `workspace-setup` использует live backend для role-management, member removal, export и delete
|
||||
- UI больше не показывает placeholder-поведение для workspace access lifecycle
|
||||
- backend дает workspace export и delete контракты, согласованные с UI
|
||||
- `test-ui` остается нетронутым как fallback
|
||||
- CI больше не собирает deployable images без использования результата
|
||||
- Deploy собирает и пушит versioned images в registry с cache
|
||||
- production deploy использует `docker compose pull && docker compose up -d`, без server-side build
|
||||
- docs и runtime config описывают registry-based rollout
|
||||
|
||||
## Next
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
admin-api:
|
||||
image: ${CRANK_ADMIN_API_IMAGE:-crank/admin-api:dev}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/admin-api/Dockerfile
|
||||
@@ -48,6 +49,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
mcp-server:
|
||||
image: ${CRANK_MCP_SERVER_IMAGE:-crank/mcp-server:dev}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/mcp-server/Dockerfile
|
||||
@@ -74,6 +76,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
ui:
|
||||
image: ${CRANK_UI_IMAGE:-crank/ui:dev}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/ui/Dockerfile
|
||||
|
||||
+22
-7
@@ -199,22 +199,37 @@ CI должен выполнять:
|
||||
CD для MVP должен:
|
||||
|
||||
- запускаться только после успешного `CI` на `main` или вручную;
|
||||
- собирать production artifacts;
|
||||
- собирать versioned container images;
|
||||
- пушить их в container registry с cache;
|
||||
- доставлять deployment files на целевой Linux host;
|
||||
- выполнять controlled restart;
|
||||
- выполнять controlled restart через `pull`, без сборки на сервере;
|
||||
- проверять health endpoints после запуска.
|
||||
|
||||
Минимальный flow:
|
||||
|
||||
1. push в `main`
|
||||
2. успешный `CI`
|
||||
3. build images или удаленная сборка на хосте
|
||||
3. build and push images в registry
|
||||
4. upload env и deployment files
|
||||
5. `docker compose config -q`
|
||||
6. `docker compose up -d`
|
||||
7. healthcheck verification для `ui`, `admin-api`, `mcp-server`
|
||||
5. `docker login` на registry на целевом хосте
|
||||
6. `docker compose config -q`
|
||||
7. `docker compose pull`
|
||||
8. `docker compose up -d`
|
||||
9. healthcheck verification для `ui`, `admin-api`, `mcp-server`
|
||||
|
||||
Для MVP допустим deployment с удаленной сборкой на целевом Linux host через `ssh + rsync + docker compose up -d --build`, если не используется отдельный container registry.
|
||||
Для текущего проекта базовая рекомендуемая схема:
|
||||
|
||||
- `GHCR` как registry;
|
||||
- versioned tags по `git sha`;
|
||||
- `docker/buildx` cache в GitHub Actions;
|
||||
- на сервере только `pull + restart`, без `docker compose up --build`.
|
||||
|
||||
Для private registry на сервере нужны отдельные deployment secrets:
|
||||
|
||||
- `DEPLOY_REGISTRY_USER`
|
||||
- `DEPLOY_REGISTRY_TOKEN`
|
||||
|
||||
Они используются только для `docker login` на целевом Linux host перед `docker compose pull`.
|
||||
|
||||
## 12. Что нельзя забыть
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ var/crank/
|
||||
Минимально ожидаются:
|
||||
|
||||
- `CRANK_DATABASE_URL`
|
||||
- `CRANK_ADMIN_API_IMAGE`
|
||||
- `CRANK_MCP_SERVER_IMAGE`
|
||||
- `CRANK_UI_IMAGE`
|
||||
- `CRANK_STORAGE_ROOT`
|
||||
- `CRANK_ADMIN_BIND`
|
||||
- `CRANK_MCP_BIND`
|
||||
@@ -116,6 +119,7 @@ Demo/deployment:
|
||||
- включенная app-level auth-защита admin-api;
|
||||
- стабильный `Streamable HTTP` endpoint для MCP.
|
||||
- containerized runtime через `Docker` и `docker-compose`.
|
||||
- registry-backed image rollout через `GHCR` или совместимый registry.
|
||||
|
||||
### Auth env
|
||||
|
||||
|
||||
Reference in New Issue
Block a user