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
|
||||
timeout-minutes: 30
|
||||
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:
|
||||
- name: Checkout deployment ref
|
||||
@@ -95,11 +100,43 @@ jobs:
|
||||
- name: Setup gcloud
|
||||
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
|
||||
run: |
|
||||
gcloud run deploy "${{ vars.CLOUD_RUN_SERVICE_BOT || 'household-bot' }}" \
|
||||
--source . \
|
||||
--region "${{ vars.GCP_REGION || 'europe-west1' }}" \
|
||||
gcloud run deploy "${CLOUD_RUN_SERVICE_BOT}" \
|
||||
--image "${{ steps.images.outputs.bot_image }}" \
|
||||
--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 }}" \
|
||||
--allow-unauthenticated \
|
||||
--quiet
|
||||
@@ -116,3 +153,4 @@ jobs:
|
||||
echo "CD skipped: configure required GitHub secrets."
|
||||
echo "Required: GCP_PROJECT_ID, GCP_WORKLOAD_IDENTITY_PROVIDER, GCP_SERVICE_ACCOUNT"
|
||||
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: |
|
||||
terraform -chdir=infra/terraform init -backend=false
|
||||
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,
|
||||
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
|
||||
```
|
||||
|
||||
## Docker smoke commands
|
||||
|
||||
```bash
|
||||
bun run docker:build
|
||||
bun run docker:smoke
|
||||
```
|
||||
|
||||
## Review commands
|
||||
|
||||
```bash
|
||||
@@ -59,12 +66,18 @@ bun run review:coderabbit
|
||||
- CI runs in parallel matrix jobs on push/PR to `main`:
|
||||
- `format:check`, `lint`, `typecheck`, `test`, `build`
|
||||
- `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 is enabled when GitHub secrets are configured:
|
||||
- `GCP_PROJECT_ID`
|
||||
- `GCP_WORKLOAD_IDENTITY_PROVIDER`
|
||||
- `GCP_SERVICE_ACCOUNT`
|
||||
- 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
|
||||
|
||||
|
||||
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:validate": "terraform -chdir=infra/terraform init -backend=false && terraform -chdir=infra/terraform validate",
|
||||
"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": {
|
||||
"@types/bun": "1.3.10",
|
||||
|
||||
Reference in New Issue
Block a user