Skip to content

Commit 35dc97d

Browse files
authored
Merge pull request #485 from ukwhatn/develop
デプロイ系改善
2 parents 2abd852 + 0b45ed8 commit 35dc97d

File tree

16 files changed

+453
-765
lines changed

16 files changed

+453
-765
lines changed

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,10 @@ AWS_ACCESS_KEY_ID=
5454
AWS_SECRET_ACCESS_KEY=
5555
AWS_REGION=auto
5656
S3_BUCKET_NAME=
57+
58+
# ========================================
59+
# GitHub設定(デプロイ用)
60+
# ========================================
61+
GITHUB_REPOSITORY=ukwhatn/fastapi-template
62+
GITHUB_USER=
63+
GITHUB_TOKEN=

.github/workflows/cd-dev.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Deploy to Dev
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
workflow_dispatch:
8+
9+
jobs:
10+
build-and-push:
11+
uses: ./.github/workflows/cd_builder.yml
12+
secrets: inherit
13+
14+
deploy:
15+
needs: build-and-push
16+
uses: ./.github/workflows/cd_deliverer.yml
17+
with:
18+
environment: dev
19+
branch: develop
20+
image_tag: develop
21+
secrets:
22+
SSH_HOST: ${{ secrets.DEV_SSH_HOST }}
23+
SSH_USER: ${{ secrets.DEV_SSH_USER }}
24+
SSH_PORT: ${{ secrets.DEV_SSH_PORT }}
25+
SSH_PRIVATE_KEY: ${{ secrets.DEV_SSH_PRIVATE_KEY }}

.github/workflows/cd-prod.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Deploy to Production
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
build-and-push:
11+
uses: ./.github/workflows/cd_builder.yml
12+
secrets: inherit
13+
14+
deploy:
15+
needs: build-and-push
16+
uses: ./.github/workflows/cd_deliverer.yml
17+
with:
18+
environment: production
19+
branch: main
20+
image_tag: latest
21+
secrets:
22+
SSH_HOST: ${{ secrets.PROD_SSH_HOST }}
23+
SSH_USER: ${{ secrets.PROD_SSH_USER }}
24+
SSH_PORT: ${{ secrets.PROD_SSH_PORT }}
25+
SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_PRIVATE_KEY }}

.github/workflows/cd_deliverer.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Deploy (Reusable)
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
environment:
7+
required: true
8+
type: string # dev or prod
9+
branch:
10+
required: true
11+
type: string # develop or main
12+
image_tag:
13+
required: true
14+
type: string # develop or latest
15+
secrets:
16+
SSH_HOST:
17+
required: false
18+
SSH_USER:
19+
required: false
20+
SSH_PORT:
21+
required: false
22+
SSH_PRIVATE_KEY:
23+
required: false
24+
25+
jobs:
26+
deploy:
27+
runs-on: ubuntu-latest
28+
environment: ${{ inputs.environment }}
29+
steps:
30+
- name: Check SSH secrets
31+
id: check-secrets
32+
run: |
33+
if [ -z "${{ secrets.SSH_HOST }}" ] || [ -z "${{ secrets.SSH_USER }}" ] || \
34+
[ -z "${{ secrets.SSH_PORT }}" ] || [ -z "${{ secrets.SSH_PRIVATE_KEY }}" ]; then
35+
echo "SSH secrets not configured. Skipping deployment."
36+
echo "skip=true" >> $GITHUB_OUTPUT
37+
else
38+
echo "SSH secrets configured. Proceeding with deployment."
39+
echo "skip=false" >> $GITHUB_OUTPUT
40+
fi
41+
42+
- name: Deploy to ${{ inputs.environment }}
43+
if: steps.check-secrets.outputs.skip != 'true'
44+
uses: appleboy/[email protected]
45+
with:
46+
host: ${{ secrets.SSH_HOST }}
47+
username: ${{ secrets.SSH_USER }}
48+
port: ${{ secrets.SSH_PORT }}
49+
key: ${{ secrets.SSH_PRIVATE_KEY }}
50+
script: |
51+
# ディレクトリ存在チェック
52+
cd ${{ github.event.repository.name }} || { echo "Directory not found. Run initial setup first."; exit 1; }
53+
54+
# 最新コード取得
55+
git pull origin ${{ inputs.branch }}
56+
57+
# .env存在チェック
58+
[ -f .env ] || { echo ".env not found. Decrypt .env.${{ inputs.environment }}.enc first."; exit 1; }
59+
60+
# イメージ取得&再起動
61+
ENV=${{ inputs.environment }} make compose:pull
62+
ENV=${{ inputs.environment }} docker compose -f compose.${{ inputs.environment }}.yml up -d --force-recreate --remove-orphans
63+
64+
# ヘルスチェック
65+
echo "Waiting for health check..."
66+
for i in {1..12}; do
67+
if ENV=${{ inputs.environment }} make compose:ps | grep -q "healthy"; then
68+
echo "Deployment successful!"
69+
exit 0
70+
fi
71+
echo "Waiting... ($((i*5))s/60s)"
72+
sleep 5
73+
done
74+
75+
echo "Health check timeout. Check logs:"
76+
ENV=${{ inputs.environment }} make compose:logs
77+
exit 1
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI & Build
1+
name: CI
22

33
on:
44
push:
@@ -31,13 +31,6 @@ jobs:
3131
uses: ./.github/workflows/ci_docker-test.yml
3232
secrets: inherit
3333

34-
# Build and push Docker image (main push or PR to main opened/reopened)
35-
build-and-push:
36-
needs: [lint, type-check, security, test, docker-test]
37-
if: github.event_name == 'push'
38-
uses: ./.github/workflows/cd_build-push.yml
39-
secrets: inherit
40-
4134
# Branch Protection Rules 用
4235
# ci-success:
4336
# runs-on: ubuntu-latest

CLAUDE.md

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,18 @@ make ps # Show running containers
9797
docker compose -f compose.local.yml up -d
9898
uv run fastapi dev app/main.py
9999

100-
# Dev environment (auto-deploy via Watchtower)
101-
make dev:deploy # Deploy to dev environment
100+
# Dev environment (auto-deploy via GitHub Actions)
101+
# Server setup (one-time): ./scripts/setup-server.sh dev
102+
# Then push to develop branch for auto-deploy
102103
make dev:logs # View dev logs
103104
make dev:ps # Check dev status
104105

105-
# Production environment (auto-deploy via Watchtower)
106-
make prod:deploy # Deploy to production (with confirmation)
106+
# Production environment (auto-deploy via GitHub Actions)
107+
# Server setup (one-time): ./scripts/setup-server.sh prod
108+
# Then push to main branch for auto-deploy
107109
make prod:logs # View production logs
108110
make prod:ps # Check production status
109111

110-
# Watchtower setup (one-time per server)
111-
./scripts/setup-watchtower.sh
112-
113112
# Secrets management (SOPS + age)
114113
make secrets:encrypt:dev # Encrypt dev secrets
115114
make secrets:encrypt:prod # Encrypt prod secrets
@@ -246,10 +245,9 @@ IMPORTANT: Follow these conventions strictly:
246245
- **Automatic execution**: Migrations run on application startup (FastAPI lifespan event)
247246
- **Location**: `app/infrastructure/database/alembic/versions/`
248247
- **Safety**: If migration fails, application startup stops (prevents data corruption)
249-
- **Watchtower compatibility**: When Watchtower updates the server image, migrations run automatically
248+
- **Auto-deploy compatibility**: When GitHub Actions deploys new image, migrations run automatically
250249
- **Idempotency**: Alembic ensures migrations only run once (safe to restart)
251250
- **Manual rollback**: Use `make db:downgrade REV=-1` if needed
252-
- **Legacy db-migrator**: Removed (migrations now embedded in server container)
253251

254252
### Docker Profiles
255253
- Use `--profile local-db` to enable local database container
@@ -263,15 +261,15 @@ IMPORTANT: Follow these conventions strictly:
263261

264262
## Deployment Architecture
265263

266-
IMPORTANT: The project uses three distinct deployment environments with automatic updates.
264+
IMPORTANT: The project uses three distinct deployment environments with GitHub Actions auto-deploy.
267265

268266
### Environment Overview
269267

270268
| Environment | App Execution | Database | Proxy | Auto-Deploy | Compose File |
271269
|-------------|--------------|----------|-------|-------------|--------------|
272270
| **Local** | uv native (hot reload) | Docker (optional) | None | No | `compose.local.yml` |
273-
| **Dev** | Docker (GHCR.io) | Docker PostgreSQL | Cloudflare Tunnels | Watchtower (develop) | `compose.dev.yml` |
274-
| **Prod** | Docker (GHCR.io) | External (Supabase) | nginx + Cloudflare | Watchtower (latest) | `compose.prod.yml` |
271+
| **Dev** | Docker (GHCR.io) | Docker PostgreSQL | Cloudflare Tunnels | GitHub Actions (develop) | `compose.dev.yml` |
272+
| **Prod** | Docker (GHCR.io) | External (Supabase) | nginx + Cloudflare | GitHub Actions (main) | `compose.prod.yml` |
275273

276274
### Key Deployment Features
277275

@@ -281,29 +279,12 @@ IMPORTANT: The project uses three distinct deployment environments with automati
281279
- `main` branch → `latest` + `main` + `main-sha-xxx` tags
282280
- `develop` branch → `develop` + `develop-sha-xxx` tags
283281

284-
**Automatic deployment**: Watchtower monitors GHCR.io and auto-updates containers (10 min polling)
282+
**Automatic deployment**: GitHub Actions SSHs into server and deploys on push to develop/main
285283

286-
**Label-based control**: Only containers with `com.centurylinklabs.watchtower.enable=true` update
284+
**Sparse checkout**: Server only clones necessary files (compose.yml, Makefile, etc.) to minimize disk usage
287285

288286
**Secrets management**: SOPS + age for encrypted environment files (`.env.dev.enc`, `.env.prod.enc`)
289287

290-
### Watchtower Setup
291-
292-
**One Watchtower per server** (not per project):
293-
- Label-based control prevents unintended updates
294-
- Weekly self-update via cron
295-
- Discord/Slack notifications via Shoutrrr
296-
- Setup: `./scripts/setup-watchtower.sh`
297-
298-
**Auto-update enabled for**:
299-
- `server` container
300-
- `db-dumper` container (from ukwhatn/postgres-tools)
301-
- `db-migrator` container (from ukwhatn/postgres-tools)
302-
303-
**Auto-update disabled for**:
304-
- `db` container (PostgreSQL)
305-
- `cloudflared` container
306-
307288
### Secrets Management (SOPS + age)
308289

309290
**Why**: Encrypted secrets can be safely committed to Git with full audit trail
@@ -316,9 +297,12 @@ IMPORTANT: The project uses three distinct deployment environments with automati
316297
5. Commit encrypted file to Git
317298

318299
**Deployment flow**:
319-
1. Deploy script decrypts `.env.dev.enc``.env`
320-
2. Starts containers with decrypted secrets
321-
3. Removes `.env` after deployment
300+
1. GitHub Actions builds image and pushes to GHCR.io
301+
2. GitHub Actions SSHs into server
302+
3. Server runs `git pull` to get latest compose.yml
303+
4. Server runs `docker compose pull` to get latest image
304+
5. Server runs `docker compose up -d --force-recreate` to restart containers
305+
6. Health check confirmation (60s timeout)
322306

323307
**Reference**: See `docs/secrets-management.md` for detailed guide
324308

@@ -332,36 +316,40 @@ uv run fastapi dev app/main.py
332316

333317
**Dev deployment** (initial):
334318
```bash
335-
./scripts/setup-watchtower.sh # One-time per server
336-
./scripts/deploy-dev.sh # Deploy
319+
# On server (one-time setup)
320+
./scripts/setup-server.sh dev
321+
322+
# Configure GitHub Secrets (one-time):
323+
# DEV_SSH_HOST, DEV_SSH_USER, DEV_SSH_PORT, DEV_SSH_PRIVATE_KEY
337324
```
338325

339326
**Dev deployment** (subsequent):
340327
```bash
341-
git push origin develop # GitHub Actions builds and pushes
342-
# Watchtower auto-updates within 10 minutes
328+
git push origin develop # GitHub Actions auto-deploys
343329
```
344330

345331
**Production deployment** (initial):
346332
```bash
347-
./scripts/setup-watchtower.sh # One-time per server
348-
./scripts/deploy-prod.sh # Deploy with confirmation
333+
# On server (one-time setup)
334+
./scripts/setup-server.sh prod
335+
336+
# Configure GitHub Secrets (one-time):
337+
# PROD_SSH_HOST, PROD_SSH_USER, PROD_SSH_PORT, PROD_SSH_PRIVATE_KEY
349338
```
350339

351340
**Production deployment** (subsequent):
352341
```bash
353-
git push origin main # GitHub Actions builds and pushes
354-
# Watchtower auto-updates within 10 minutes
342+
git push origin main # GitHub Actions auto-deploys
355343
```
356344

357345
### Important Deployment Notes
358346

359-
- **No SSH required**: All deployments use GHCR.io pull + Watchtower
360-
- **Downtime**: 10-30 seconds during auto-updates
361-
- **Branch isolation**: develop and main branches use different tags (no cross-contamination)
362-
- **Rollback**: Use SHA tags for specific version deployment
363-
- **Monitoring**: Check Watchtower logs with `docker logs watchtower -f`
364-
- **Health checks**: Containers have built-in health checks; Watchtower respects them
347+
- **SSH managed by GitHub Actions**: Developers don't need server access
348+
- **Downtime**: ~10-30 seconds during deploys (forced container recreation)
349+
- **Branch isolation**: develop and main branches use different tags and servers
350+
- **Rollback**: Use `git revert` and push, or manually checkout previous commit on server
351+
- **Monitoring**: Check GitHub Actions logs and server logs with `ENV={dev|prod} make compose:logs`
352+
- **Health checks**: Deployment fails if health check doesn't pass within 60 seconds
365353

366354
**Reference**: See `docs/deployment.md` for comprehensive deployment guide
367355

Makefile

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -277,19 +277,6 @@ prod\:ps:
277277
prod\:down:
278278
@ENV=prod $(MAKE) compose:down
279279

280-
# ==== Watchtower管理コマンド ====
281-
watchtower\:setup:
282-
@./scripts/setup-watchtower.sh
283-
284-
watchtower\:logs:
285-
@docker logs watchtower -f
286-
287-
watchtower\:status:
288-
@docker ps --filter "name=watchtower"
289-
290-
watchtower\:restart:
291-
@docker restart watchtower
292-
293280
# ==== 秘密情報管理コマンド ====
294281
secrets\:encrypt\:dev:
295282
@if [ ! -f .env.dev ]; then \
@@ -380,4 +367,4 @@ template\:apply\:force:
380367
git checkout $$commit_hash -- . && \
381368
echo "テンプレートの変更が強制的に適用されました。変更を確認しgit add/commitしてください。"
382369

383-
.PHONY: build build\:no-cache up down reload reset logs logs\:once ps pr\:create deploy\:prod uv\:add uv\:add\:dev uv\:lock uv\:update uv\:update\:all dev\:setup lint lint\:fix format format\:check type-check security\:scan security\:scan\:code security\:scan\:code\:critical security\:scan\:sast security\:scan\:sast\:critical security\:scan\:trivy test test\:cov db\:revision\:create db\:migrate db\:downgrade db\:current db\:history db\:backup\:oneshot db\:backup\:list db\:backup\:list\:remote db\:backup\:diff db\:backup\:diff\:s3 db\:backup\:restore db\:backup\:restore\:s3 db\:backup\:restore\:dry-run env openapi\:generate compose\:up compose\:down compose\:down\:v compose\:logs compose\:ps compose\:pull compose\:restart compose\:build local\:up local\:down local\:down\:v local\:logs local\:ps local\:serve dev\:deploy dev\:logs dev\:ps dev\:down prod\:deploy prod\:logs prod\:ps prod\:down watchtower\:setup watchtower\:logs watchtower\:status watchtower\:restart secrets\:encrypt\:dev secrets\:encrypt\:prod secrets\:decrypt\:dev secrets\:decrypt\:prod secrets\:edit\:dev secrets\:edit\:prod project\:rename project\:init template\:list template\:apply template\:apply\:range template\:apply\:force pre-commit\:install pre-commit\:run pre-commit\:update
370+
.PHONY: build build\:no-cache up down reload reset logs logs\:once ps pr\:create deploy\:prod uv\:add uv\:add\:dev uv\:lock uv\:update uv\:update\:all dev\:setup lint lint\:fix format format\:check type-check security\:scan security\:scan\:code security\:scan\:code\:critical security\:scan\:sast security\:scan\:sast\:critical security\:scan\:trivy test test\:cov db\:revision\:create db\:migrate db\:downgrade db\:current db\:history db\:backup\:oneshot db\:backup\:list db\:backup\:list\:remote db\:backup\:diff db\:backup\:diff\:s3 db\:backup\:restore db\:backup\:restore\:s3 db\:backup\:restore\:dry-run env openapi\:generate compose\:up compose\:down compose\:down\:v compose\:logs compose\:ps compose\:pull compose\:restart compose\:build local\:up local\:down local\:down\:v local\:logs local\:ps local\:serve dev\:deploy dev\:logs dev\:ps dev\:down prod\:deploy prod\:logs prod\:ps prod\:down secrets\:encrypt\:dev secrets\:encrypt\:prod secrets\:decrypt\:dev secrets\:decrypt\:prod secrets\:edit\:dev secrets\:edit\:prod project\:rename project\:init template\:list template\:apply template\:apply\:range template\:apply\:force pre-commit\:install pre-commit\:run pre-commit\:update

compose.dev.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
services:
1212
server:
1313
container_name: ${COMPOSE_PROJECT_NAME:-fastapi-template}-server-dev
14-
image: ghcr.io/${GITHUB_REPOSITORY:-owner/repo}:develop
14+
image: ghcr.io/${GITHUB_REPOSITORY:-ukwhatn/fastapi-template}:develop
1515
command:
1616
- /bin/sh
1717
- -c
@@ -36,8 +36,6 @@ services:
3636
required: false
3737
networks:
3838
- db-dev
39-
labels:
40-
- "com.centurylinklabs.watchtower.enable=true"
4139

4240
db:
4341
container_name: ${COMPOSE_PROJECT_NAME:-fastapi-template}-db-dev
@@ -59,8 +57,6 @@ services:
5957
- db-dev
6058
profiles:
6159
- local-db
62-
labels:
63-
- "com.centurylinklabs.watchtower.enable=false"
6460

6561
volumes:
6662
pg_data_dev:

0 commit comments

Comments
 (0)