mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 09:14:02 +00:00
feat(infra): add docker image build and deploy pipeline (#13)
This commit is contained in:
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.linear
|
||||||
|
.codex
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
|
infra/terraform/.terraform
|
||||||
|
infra/terraform/.terraform.lock.hcl
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.DS_Store
|
||||||
44
.github/workflows/cd.yml
vendored
44
.github/workflows/cd.yml
vendored
@@ -63,6 +63,11 @@ jobs:
|
|||||||
needs: check-secrets
|
needs: check-secrets
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
if: ${{ needs.check-secrets.outputs.eligible_event == 'true' && needs.check-secrets.outputs.secrets_ok == 'true' }}
|
if: ${{ needs.check-secrets.outputs.eligible_event == 'true' && needs.check-secrets.outputs.secrets_ok == 'true' }}
|
||||||
|
env:
|
||||||
|
GCP_REGION: ${{ vars.GCP_REGION || 'europe-west1' }}
|
||||||
|
ARTIFACT_REPOSITORY: ${{ vars.ARTIFACT_REPOSITORY || 'household-bot' }}
|
||||||
|
CLOUD_RUN_SERVICE_BOT: ${{ vars.CLOUD_RUN_SERVICE_BOT || 'household-dev-bot-api' }}
|
||||||
|
CLOUD_RUN_SERVICE_MINI: ${{ vars.CLOUD_RUN_SERVICE_MINI || 'household-dev-mini-app' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout deployment ref
|
- name: Checkout deployment ref
|
||||||
@@ -95,11 +100,43 @@ jobs:
|
|||||||
- name: Setup gcloud
|
- name: Setup gcloud
|
||||||
uses: google-github-actions/setup-gcloud@v2
|
uses: google-github-actions/setup-gcloud@v2
|
||||||
|
|
||||||
|
- name: Configure Artifact Registry auth
|
||||||
|
run: |
|
||||||
|
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet
|
||||||
|
|
||||||
|
- name: Resolve image tags
|
||||||
|
id: images
|
||||||
|
run: |
|
||||||
|
bot_image="${GCP_REGION}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${ARTIFACT_REPOSITORY}/bot:${GITHUB_SHA}"
|
||||||
|
mini_image="${GCP_REGION}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${ARTIFACT_REPOSITORY}/miniapp:${GITHUB_SHA}"
|
||||||
|
|
||||||
|
echo "bot_image=$bot_image" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "mini_image=$mini_image" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Build and push bot image
|
||||||
|
run: |
|
||||||
|
docker build -f apps/bot/Dockerfile -t "${{ steps.images.outputs.bot_image }}" .
|
||||||
|
docker push "${{ steps.images.outputs.bot_image }}"
|
||||||
|
|
||||||
|
- name: Build and push mini app image
|
||||||
|
run: |
|
||||||
|
docker build -f apps/miniapp/Dockerfile -t "${{ steps.images.outputs.mini_image }}" .
|
||||||
|
docker push "${{ steps.images.outputs.mini_image }}"
|
||||||
|
|
||||||
- name: Deploy bot service
|
- name: Deploy bot service
|
||||||
run: |
|
run: |
|
||||||
gcloud run deploy "${{ vars.CLOUD_RUN_SERVICE_BOT || 'household-bot' }}" \
|
gcloud run deploy "${CLOUD_RUN_SERVICE_BOT}" \
|
||||||
--source . \
|
--image "${{ steps.images.outputs.bot_image }}" \
|
||||||
--region "${{ vars.GCP_REGION || 'europe-west1' }}" \
|
--region "${GCP_REGION}" \
|
||||||
|
--project "${{ secrets.GCP_PROJECT_ID }}" \
|
||||||
|
--allow-unauthenticated \
|
||||||
|
--quiet
|
||||||
|
|
||||||
|
- name: Deploy mini app service
|
||||||
|
run: |
|
||||||
|
gcloud run deploy "${CLOUD_RUN_SERVICE_MINI}" \
|
||||||
|
--image "${{ steps.images.outputs.mini_image }}" \
|
||||||
|
--region "${GCP_REGION}" \
|
||||||
--project "${{ secrets.GCP_PROJECT_ID }}" \
|
--project "${{ secrets.GCP_PROJECT_ID }}" \
|
||||||
--allow-unauthenticated \
|
--allow-unauthenticated \
|
||||||
--quiet
|
--quiet
|
||||||
@@ -116,3 +153,4 @@ jobs:
|
|||||||
echo "CD skipped: configure required GitHub secrets."
|
echo "CD skipped: configure required GitHub secrets."
|
||||||
echo "Required: GCP_PROJECT_ID, GCP_WORKLOAD_IDENTITY_PROVIDER, GCP_SERVICE_ACCOUNT"
|
echo "Required: GCP_PROJECT_ID, GCP_WORKLOAD_IDENTITY_PROVIDER, GCP_SERVICE_ACCOUNT"
|
||||||
echo "Optional for auto-migrations: DATABASE_URL"
|
echo "Optional for auto-migrations: DATABASE_URL"
|
||||||
|
echo "Optional repo/service vars: GCP_REGION, ARTIFACT_REPOSITORY, CLOUD_RUN_SERVICE_BOT, CLOUD_RUN_SERVICE_MINI"
|
||||||
|
|||||||
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -101,3 +101,36 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
terraform -chdir=infra/terraform init -backend=false
|
terraform -chdir=infra/terraform init -backend=false
|
||||||
terraform -chdir=infra/terraform validate
|
terraform -chdir=infra/terraform validate
|
||||||
|
|
||||||
|
images:
|
||||||
|
name: Docker / build ${{ matrix.service }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
service:
|
||||||
|
- bot
|
||||||
|
- miniapp
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build container image
|
||||||
|
run: |
|
||||||
|
case "${{ matrix.service }}" in
|
||||||
|
bot)
|
||||||
|
docker build -f apps/bot/Dockerfile -t household-bot:ci .
|
||||||
|
;;
|
||||||
|
miniapp)
|
||||||
|
docker build -f apps/miniapp/Dockerfile -t household-miniapp:ci .
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown service: ${{ matrix.service }}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
@@ -4,3 +4,10 @@ Telegram household finance bot and mini app built with Bun workspaces.
|
|||||||
|
|
||||||
See the [development setup runbook](docs/runbooks/dev-setup.md) for full setup,
|
See the [development setup runbook](docs/runbooks/dev-setup.md) for full setup,
|
||||||
quality-check, and local development commands.
|
quality-check, and local development commands.
|
||||||
|
|
||||||
|
For container smoke testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build
|
||||||
|
bun run docker:smoke
|
||||||
|
```
|
||||||
|
|||||||
40
apps/bot/Dockerfile
Normal file
40
apps/bot/Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
FROM oven/bun:1.3.10 AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY bun.lock package.json tsconfig.base.json ./
|
||||||
|
COPY apps/bot/package.json apps/bot/package.json
|
||||||
|
COPY apps/miniapp/package.json apps/miniapp/package.json
|
||||||
|
COPY packages/application/package.json packages/application/package.json
|
||||||
|
COPY packages/config/package.json packages/config/package.json
|
||||||
|
COPY packages/contracts/package.json packages/contracts/package.json
|
||||||
|
COPY packages/db/package.json packages/db/package.json
|
||||||
|
COPY packages/domain/package.json packages/domain/package.json
|
||||||
|
COPY packages/observability/package.json packages/observability/package.json
|
||||||
|
COPY packages/ports/package.json packages/ports/package.json
|
||||||
|
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM deps AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY apps ./apps
|
||||||
|
COPY packages ./packages
|
||||||
|
|
||||||
|
RUN bun run --filter @household/bot build
|
||||||
|
|
||||||
|
FROM oven/bun:1.3.10 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=8080
|
||||||
|
|
||||||
|
COPY --from=build /app/apps/bot/dist ./apps/bot/dist
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD bun -e "fetch('http://127.0.0.1:' + (process.env.PORT ?? '8080') + '/health').then((res) => process.exit(res.ok ? 0 : 1)).catch(() => process.exit(1))"
|
||||||
|
|
||||||
|
CMD ["bun", "apps/bot/dist/index.js"]
|
||||||
34
apps/miniapp/Dockerfile
Normal file
34
apps/miniapp/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
FROM oven/bun:1.3.10 AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY bun.lock package.json tsconfig.base.json ./
|
||||||
|
COPY apps/bot/package.json apps/bot/package.json
|
||||||
|
COPY apps/miniapp/package.json apps/miniapp/package.json
|
||||||
|
COPY packages/application/package.json packages/application/package.json
|
||||||
|
COPY packages/config/package.json packages/config/package.json
|
||||||
|
COPY packages/contracts/package.json packages/contracts/package.json
|
||||||
|
COPY packages/db/package.json packages/db/package.json
|
||||||
|
COPY packages/domain/package.json packages/domain/package.json
|
||||||
|
COPY packages/observability/package.json packages/observability/package.json
|
||||||
|
COPY packages/ports/package.json packages/ports/package.json
|
||||||
|
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM deps AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY apps/miniapp ./apps/miniapp
|
||||||
|
|
||||||
|
RUN bun run --filter @household/miniapp build
|
||||||
|
|
||||||
|
FROM nginx:1.27-alpine AS runtime
|
||||||
|
|
||||||
|
COPY apps/miniapp/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY --from=build /app/apps/miniapp/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD wget -qO- http://127.0.0.1:8080/health >/dev/null || exit 1
|
||||||
16
apps/miniapp/nginx.conf
Normal file
16
apps/miniapp/nginx.conf
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location = /health {
|
||||||
|
default_type application/json;
|
||||||
|
return 200 '{"ok":true}';
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/bot/Dockerfile
|
||||||
|
environment:
|
||||||
|
PORT: '8080'
|
||||||
|
TELEGRAM_BOT_TOKEN: '${TELEGRAM_BOT_TOKEN:-000000:dev-token}'
|
||||||
|
TELEGRAM_WEBHOOK_SECRET: '${TELEGRAM_WEBHOOK_SECRET:-dev-secret}'
|
||||||
|
TELEGRAM_WEBHOOK_PATH: '${TELEGRAM_WEBHOOK_PATH:-/webhook/telegram}'
|
||||||
|
ports:
|
||||||
|
- '8080:8080'
|
||||||
|
|
||||||
|
miniapp:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/miniapp/Dockerfile
|
||||||
|
ports:
|
||||||
|
- '8081:8080'
|
||||||
@@ -37,6 +37,13 @@ bun run dev:bot
|
|||||||
bun run dev:miniapp
|
bun run dev:miniapp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker smoke commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build
|
||||||
|
bun run docker:smoke
|
||||||
|
```
|
||||||
|
|
||||||
## Review commands
|
## Review commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -59,12 +66,18 @@ bun run review:coderabbit
|
|||||||
- CI runs in parallel matrix jobs on push/PR to `main`:
|
- CI runs in parallel matrix jobs on push/PR to `main`:
|
||||||
- `format:check`, `lint`, `typecheck`, `test`, `build`
|
- `format:check`, `lint`, `typecheck`, `test`, `build`
|
||||||
- `terraform fmt -check`, `terraform validate`
|
- `terraform fmt -check`, `terraform validate`
|
||||||
|
- docker image builds for `apps/bot` and `apps/miniapp`
|
||||||
- CD deploys on successful `main` CI completion (or manual dispatch).
|
- CD deploys on successful `main` CI completion (or manual dispatch).
|
||||||
- CD is enabled when GitHub secrets are configured:
|
- CD is enabled when GitHub secrets are configured:
|
||||||
- `GCP_PROJECT_ID`
|
- `GCP_PROJECT_ID`
|
||||||
- `GCP_WORKLOAD_IDENTITY_PROVIDER`
|
- `GCP_WORKLOAD_IDENTITY_PROVIDER`
|
||||||
- `GCP_SERVICE_ACCOUNT`
|
- `GCP_SERVICE_ACCOUNT`
|
||||||
- optional for automated migrations: `DATABASE_URL`
|
- optional for automated migrations: `DATABASE_URL`
|
||||||
|
- Optional GitHub variables for deploy:
|
||||||
|
- `GCP_REGION` (default `europe-west1`)
|
||||||
|
- `ARTIFACT_REPOSITORY` (default `household-bot`)
|
||||||
|
- `CLOUD_RUN_SERVICE_BOT` (default `household-dev-bot-api`)
|
||||||
|
- `CLOUD_RUN_SERVICE_MINI` (default `household-dev-mini-app`)
|
||||||
|
|
||||||
## IaC Runbook
|
## IaC Runbook
|
||||||
|
|
||||||
|
|||||||
72
docs/specs/HOUSEBOT-060-docker-image-pipeline.md
Normal file
72
docs/specs/HOUSEBOT-060-docker-image-pipeline.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# HOUSEBOT-060: Docker Images for Bot and Mini App
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Add production Docker images and CI/CD image flow so both services are deployable to Cloud Run from Artifact Registry.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Add reproducible Dockerfiles for `apps/bot` and `apps/miniapp`.
|
||||||
|
- Provide local Docker smoke execution for both services.
|
||||||
|
- Build images in CI and deploy Cloud Run from pushed images in CD.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Kubernetes manifests.
|
||||||
|
- Full production runbook and cutover checklist.
|
||||||
|
- Runtime feature changes in bot or mini app business logic.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- In: Dockerfiles, nginx config for SPA serving, compose smoke setup, CI/CD workflow updates, developer scripts/docs.
|
||||||
|
- Out: Advanced image signing/SBOM/scanning.
|
||||||
|
|
||||||
|
## Interfaces and Contracts
|
||||||
|
|
||||||
|
- Bot container exposes `PORT` (default `8080`) and `/health`.
|
||||||
|
- Mini app container serves SPA on `8080` and provides `/health`.
|
||||||
|
- CD builds and pushes:
|
||||||
|
- `<region>-docker.pkg.dev/<project>/<repo>/bot:<sha>`
|
||||||
|
- `<region>-docker.pkg.dev/<project>/<repo>/miniapp:<sha>`
|
||||||
|
|
||||||
|
## Domain Rules
|
||||||
|
|
||||||
|
- None (infrastructure change).
|
||||||
|
|
||||||
|
## Data Model Changes
|
||||||
|
|
||||||
|
- None.
|
||||||
|
|
||||||
|
## Security and Privacy
|
||||||
|
|
||||||
|
- No secrets embedded in images.
|
||||||
|
- Runtime secrets remain injected via Cloud Run/Secret Manager.
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
- Container health checks for bot and mini app.
|
||||||
|
- CD logs include image refs and deploy steps.
|
||||||
|
|
||||||
|
## Edge Cases and Failure Modes
|
||||||
|
|
||||||
|
- Missing Artifact Registry repository: image push fails.
|
||||||
|
- Missing Cloud Run service vars: deploy falls back to documented defaults.
|
||||||
|
- Missing DB secret: migrations are skipped but deploy continues.
|
||||||
|
|
||||||
|
## Test Plan
|
||||||
|
|
||||||
|
- Unit: N/A.
|
||||||
|
- Integration: CI docker build jobs for both images.
|
||||||
|
- E2E: local `docker compose up --build` smoke run with health endpoint checks.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Both services run locally via Docker.
|
||||||
|
- [ ] CI builds both images without manual patching.
|
||||||
|
- [ ] CD deploys Cloud Run from built Artifact Registry images.
|
||||||
|
|
||||||
|
## Rollout Plan
|
||||||
|
|
||||||
|
- Merge Docker + workflow changes.
|
||||||
|
- Configure optional GitHub vars (`GCP_REGION`, `ARTIFACT_REPOSITORY`, service names).
|
||||||
|
- Trigger `workflow_dispatch` CD once to validate image deploy path.
|
||||||
@@ -25,7 +25,11 @@
|
|||||||
"infra:fmt:check": "terraform -chdir=infra/terraform fmt -check -recursive",
|
"infra:fmt:check": "terraform -chdir=infra/terraform fmt -check -recursive",
|
||||||
"infra:validate": "terraform -chdir=infra/terraform init -backend=false && terraform -chdir=infra/terraform validate",
|
"infra:validate": "terraform -chdir=infra/terraform init -backend=false && terraform -chdir=infra/terraform validate",
|
||||||
"dev:bot": "bun run --filter @household/bot dev",
|
"dev:bot": "bun run --filter @household/bot dev",
|
||||||
"dev:miniapp": "bun run --filter @household/miniapp dev"
|
"dev:miniapp": "bun run --filter @household/miniapp dev",
|
||||||
|
"docker:build:bot": "docker build -f apps/bot/Dockerfile -t household-bot:local .",
|
||||||
|
"docker:build:miniapp": "docker build -f apps/miniapp/Dockerfile -t household-miniapp:local .",
|
||||||
|
"docker:build": "bun run docker:build:bot && bun run docker:build:miniapp",
|
||||||
|
"docker:smoke": "docker compose up --build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "1.3.10",
|
"@types/bun": "1.3.10",
|
||||||
|
|||||||
Reference in New Issue
Block a user