Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,37 @@ jobs:
assert len(tables) >= 4, f'Expected at least 4 tables, got {len(tables)}: {tables}'
conn.close()
"

docker-build:
name: Docker Build Smoke Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build API image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.api
push: false
cache-from: type=gha,scope=api
cache-to: type=gha,mode=max,scope=api
- name: Build Worker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.worker
push: false
cache-from: type=gha,scope=worker
cache-to: type=gha,mode=max,scope=worker
- name: Build Web image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.web
push: false
cache-from: type=gha,scope=web
cache-to: type=gha,mode=max,scope=web
78 changes: 60 additions & 18 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,67 @@ on:
push:
branches: [main]

concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/mriechers/cardigan

jobs:
deploy:
name: Deploy to Production
build-and-push:
name: Build & Push Images
runs-on: ubuntu-latest
env:
DEPLOY_SHA: ${{ github.sha }}
DEPLOY_BRANCH: ${{ github.ref_name }}
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4
- name: Deploy placeholder
run: |
echo "========================================="
echo " Deploy would happen here"
echo " Commit: $DEPLOY_SHA"
echo " Branch: $DEPLOY_BRANCH"
echo "========================================="
echo ""
echo "Future steps:"
echo " 1. Build Docker image"
echo " 2. Push to container registry"
echo " 3. Deploy to production server"
echo " 4. Run health checks"

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build & push API image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.api
push: true
tags: |
${{ env.IMAGE_PREFIX }}-api:latest
${{ env.IMAGE_PREFIX }}-api:${{ github.sha }}
cache-from: type=gha,scope=deploy-api
cache-to: type=gha,mode=max,scope=deploy-api

- name: Build & push Worker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.worker
push: true
tags: |
${{ env.IMAGE_PREFIX }}-worker:latest
${{ env.IMAGE_PREFIX }}-worker:${{ github.sha }}
cache-from: type=gha,scope=deploy-worker
cache-to: type=gha,mode=max,scope=deploy-worker

- name: Build & push Web image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.web
push: true
tags: |
${{ env.IMAGE_PREFIX }}-web:latest
${{ env.IMAGE_PREFIX }}-web:${{ github.sha }}
cache-from: type=gha,scope=deploy-web
cache-to: type=gha,mode=max,scope=deploy-web
119 changes: 119 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Production deployment — pulls pre-built images from GHCR.
# Usage: docker compose -f docker-compose.prod.yml up -d
#
# Requires:
# - .env file with API keys and CARDIGAN_API_KEY
# - docker login to ghcr.io (see Task 7 in the plan)

services:
api:
image: ghcr.io/mriechers/cardigan-api:latest
ports:
- "8000:8000"
env_file: .env
environment:
- DATABASE_PATH=/data/db/dashboard.db
- OUTPUT_DIR=/data/output
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000}
- CARDIGAN_API_KEY=${CARDIGAN_API_KEY}
- TRANSCRIPTS_DIR=/data/transcripts
volumes:
- db-data:/data/db
- output-data:/data/output
- transcript-data:/data/transcripts
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/system/health')"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
labels:
- "com.centurylinklabs.watchtower.enable=true"
restart: unless-stopped

worker:
image: ghcr.io/mriechers/cardigan-worker:latest
env_file: .env
environment:
- DATABASE_PATH=/data/db/dashboard.db
- OUTPUT_DIR=/data/output
- CARDIGAN_API_KEY=${CARDIGAN_API_KEY}
- TRANSCRIPTS_DIR=/data/transcripts
volumes:
- db-data:/data/db
- output-data:/data/output
- transcript-data:/data/transcripts
depends_on:
api:
condition: service_healthy
labels:
- "com.centurylinklabs.watchtower.enable=true"
restart: unless-stopped

web:
image: ghcr.io/mriechers/cardigan-web:latest
ports:
- "3000:3000"
environment:
- CARDIGAN_API_KEY=${CARDIGAN_API_KEY}
depends_on:
- api
labels:
- "com.centurylinklabs.watchtower.enable=true"
restart: unless-stopped

mcp:
image: ghcr.io/mriechers/cardigan-api:latest
command: ["python", "-m", "mcp_server.server"]
ports:
- "8080:8080"
env_file: .env
environment:
- MCP_TRANSPORT=sse
- EDITORIAL_API_URL=http://api:8000
- DATABASE_PATH=/data/db/dashboard.db
- OUTPUT_DIR=/data/output
- TRANSCRIPTS_DIR=/data/transcripts
volumes:
- db-data:/data/db
- output-data:/data/output
- transcript-data:/data/transcripts
depends_on:
api:
condition: service_healthy
profiles:
- mcp
restart: unless-stopped

tunnel:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
depends_on:
api:
condition: service_healthy
web:
condition: service_started
profiles:
- tunnel
restart: unless-stopped

watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Set DOCKER_CONFIG in .env if Docker runs as non-root user
# (e.g., DOCKER_CONFIG=/home/ubuntu/.docker)
- ${DOCKER_CONFIG:-/root/.docker}/config.json:/config.json:ro
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=300
# Only update containers with the watchtower.enable=true label
- WATCHTOWER_LABEL_ENABLE=true
restart: unless-stopped

volumes:
db-data:
output-data:
transcript-data:
Loading