Skip to content
Merged
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
192 changes: 192 additions & 0 deletions .github/workflows/publish-webapp-ghcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
name: Build and Push Webapp to GHCR

on:
push:
branches:
- main
- 'claude/**'
paths:
- 'webapp/**'
- 'knowledgebase_processor/**'
- '.github/workflows/publish-webapp-ghcr.yml'
pull_request:
branches:
- main
paths:
- 'webapp/**'
- 'knowledgebase_processor/**'
workflow_dispatch:
inputs:
tag:
description: 'Custom tag for the image'
required: false
default: 'latest'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/kb-processor-webapp

jobs:
build-and-push:
name: Build and Push to GHCR
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

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

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
run: |
# Get short SHA
SHORT_SHA=$(git rev-parse --short HEAD)
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT

# Get branch name
BRANCH_NAME=${GITHUB_REF##*/}
echo "branch=${BRANCH_NAME}" >> $GITHUB_OUTPUT

# Get timestamp
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT

# Get repository name (lowercase)
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
echo "repo_lower=${REPO_LOWER}" >> $GITHUB_OUTPUT

# Determine tags
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ github.event.inputs.tag }}" ]]; then
CUSTOM_TAG="${{ github.event.inputs.tag }}"
echo "custom_tag=${CUSTOM_TAG}" >> $GITHUB_OUTPUT
fi

- name: Build Docker image tags
id: docker_tags
run: |
GHCR_IMAGE="ghcr.io/${{ steps.meta.outputs.repo_lower }}/kb-processor-webapp"

TAGS="${GHCR_IMAGE}:${{ steps.meta.outputs.short_sha }}"
TAGS="${TAGS},${GHCR_IMAGE}:${{ steps.meta.outputs.timestamp }}"

# Add branch-specific tag
if [[ "${{ steps.meta.outputs.branch }}" == "main" ]]; then
TAGS="${TAGS},${GHCR_IMAGE}:latest"
TAGS="${TAGS},${GHCR_IMAGE}:stable"
else
SAFE_BRANCH=$(echo "${{ steps.meta.outputs.branch }}" | sed 's/\//-/g')
TAGS="${TAGS},${GHCR_IMAGE}:${SAFE_BRANCH}"
fi

# Add custom tag if provided
if [[ -n "${{ steps.meta.outputs.custom_tag }}" ]]; then
TAGS="${TAGS},${GHCR_IMAGE}:${{ steps.meta.outputs.custom_tag }}"
fi

echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "ghcr_image=${GHCR_IMAGE}" >> $GITHUB_OUTPUT

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./webapp/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
labels: |
org.opencontainers.image.title=Knowledge Base Processor Webapp
org.opencontainers.image.description=Web interface for the Knowledge Base Processor
org.opencontainers.image.url=https://github.com/${{ github.repository }}
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ steps.meta.outputs.timestamp }}
org.opencontainers.image.licenses=MIT

- name: Generate deployment summary
if: github.event_name != 'pull_request'
run: |
echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Image Details" >> $GITHUB_STEP_SUMMARY
echo "- **Registry:** GitHub Container Registry (GHCR)" >> $GITHUB_STEP_SUMMARY
echo "- **Image:** \`${{ steps.docker_tags.outputs.ghcr_image }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Commit SHA:** \`${{ steps.meta.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Branch:** \`${{ steps.meta.outputs.branch }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Timestamp:** \`${{ steps.meta.outputs.timestamp }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🏷️ Tags" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.docker_tags.outputs.tags }}" | tr ',' '\n' >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Pull Command" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ steps.docker_tags.outputs.ghcr_image }}:${{ steps.meta.outputs.short_sha }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🚀 Run Command" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker run -p 8000:8000 ${{ steps.docker_tags.outputs.ghcr_image }}:${{ steps.meta.outputs.short_sha }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Package URL" >> $GITHUB_STEP_SUMMARY
echo "https://github.com/${{ github.repository }}/pkgs/container/kb-processor-webapp" >> $GITHUB_STEP_SUMMARY

- name: Output image URL
if: github.event_name != 'pull_request'
run: |
echo "✅ Image published successfully!"
echo "📍 Image URL: ${{ steps.docker_tags.outputs.ghcr_image }}:${{ steps.meta.outputs.short_sha }}"
echo "🌐 Access the webapp at: http://localhost:8000 (after running the container)"
echo "📦 View package: https://github.com/${{ github.repository }}/pkgs/container/kb-processor-webapp"

verify-image:
name: Verify Published Image
needs: build-and-push
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'

permissions:
packages: read

steps:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Verify image exists
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
IMAGE="ghcr.io/${REPO_LOWER}/kb-processor-webapp:${SHORT_SHA}"

echo "Verifying image: ${IMAGE}"

if docker manifest inspect ${IMAGE} > /dev/null 2>&1; then
echo "✅ Image verified successfully!"
else
echo "❌ Image verification failed!"
exit 1
fi
55 changes: 55 additions & 0 deletions webapp/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
*.egg

# Virtual environments
env/
venv/
ENV/
.venv

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Git
.git/
.gitignore
.gitattributes

# Documentation
*.md
!README.md

# Testing
.pytest_cache/
.coverage
htmlcov/
*.log

# Docker
Dockerfile
.dockerignore
docker-compose.yml

# Database
*.db
*.sqlite

# Environment
.env
.env.*
15 changes: 15 additions & 0 deletions webapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
*.egg-info/
.env
.venv
*.db
*.sqlite
*.log
node_modules/
.DS_Store
51 changes: 51 additions & 0 deletions webapp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Multi-stage build for Knowledge Base Processor Webapp
FROM python:3.12-slim AS builder

# Set working directory
WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*

# Copy the entire project (needed for package installation)
COPY .. /app

# Install the main package and webapp dependencies
RUN pip install --no-cache-dir --user -e . && \
pip install --no-cache-dir --user \
fastapi>=0.104.0 \
uvicorn[standard]>=0.24.0 \
python-multipart>=0.0.6 \
jinja2>=3.1.2

# Production stage
FROM python:3.12-slim

# Set working directory
WORKDIR /app

# Copy Python packages from builder
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app /app

# Make sure scripts in .local are usable
ENV PATH=/root/.local/bin:$PATH

# Set Python path to find the package
ENV PYTHONPATH=/app:$PYTHONPATH

# Change to webapp directory
WORKDIR /app/webapp

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/stats')" || exit 1

# Run the application
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
Loading
Loading