feat(infra): add docker image build and deploy pipeline (#13)

This commit is contained in:
Stas
2026-03-05 04:01:08 +03:00
committed by GitHub
parent fad17b690f
commit 4ecafcfe23
11 changed files with 293 additions and 4 deletions

13
.dockerignore Normal file
View 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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'

View File

@@ -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

View 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.

View File

@@ -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",