feat: modernize deployment pipeline for Komodo integration

Production Deployment Improvements:
- Added .env.example template for secure credential management
- Modernized Docker build workflow with branch sanitization
- Created Komodo deployment trigger workflow
- Updated compose.yaml with environment variable substitution

GitHub Workflows:
- Updated docker-image.yml:
  - Add branch name sanitization (replace / with -)
  - Generate both date-tagged and -latest Docker tags
  - Upgrade to actions/checkout@v4
  - Add pull: true for layer caching

- New komodo-deploy.yml:
  - Triggers after successful Docker Image CI
  - Sends signed webhook to Komodo service
  - Extracts and sanitizes branch names
  - Requires secrets: KOMODO_HOST, KOMODO_STACK_ID_OR_NAME, KOMODO_WEBHOOK_SECRET

Docker Compose Modernization:
- Dynamic IMAGE_TAG with sensible defaults (main-latest)
- Configurable memory limits and reservations
- Environment variable substitution for all configs
- Added container names and restart policies
- Improved healthcheck with Host header
- Updated Traefik labels with Let's Encrypt cert resolver
- Added loadbalancer server URL configuration
- Explicit command paths for reliability

Security:
- .env.example provides safe template (no credentials)
- Actual .env remains in .gitignore (not tracked)

Pattern based on proven Formana deployment architecture.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 16:30:10 -05:00
co-authored by Claude Sonnet 4.5
parent 8e514ded43
commit b129cd4f0c
4 changed files with 125 additions and 26 deletions
+40
View File
@@ -0,0 +1,40 @@
# OpenAI Configuration
OPENAI_API_KEY=sk-proj-your-openai-api-key-here
OPENAI_MODEL=gpt-4o-mini
# OPENAI_MODEL=gpt-4o
# Security
HTTPS_ONLY=False
SECRET_KEY=your-secret-key-here
# Local Database Configuration
LOCAL_DB_UN=pygentic_ai
LOCAL_DB_PW=your-local-db-password
LOCAL_DB_DB=fsecada_server
LOCAL_DB_HOST=localhost
LOCAL_DB_PORT=5433
# Cloud Database Configuration
CLOUD_DB_UN=pygentic_ai
CLOUD_DB_PW=your-cloud-db-password
CLOUD_DB_DB=fsecada_server
CLOUD_DB_HOST=pgserver.example.com
CLOUD_DB_PORT=5433
# Database Configuration
SQL_DIALECT=postgresql
# Application Environment
DEBUG=True
SERVER_ENV=dev
# Reddit API Configuration (for intelligence gathering)
REDDIT_MAX_INSIGHTS=5
REDDIT_MAX_INSIGHT_LENGTH=400
REDDIT_USER_AGENT=your-reddit-user-agent
REDDIT_CLIENT_ID=your-reddit-client-id
REDDIT_CLIENT_SECRET=your-reddit-client-secret
REDDIT_SUBREDDIT=python,kubernetes,devops,rust,selfhosted,algorithms,typescript,programming,webdev,strategy,DigitalMarketing,Entrepreneur
# Celery Configuration (optional)
# CELERY_PORT=5052
+10 -3
View File
@@ -24,13 +24,17 @@ jobs:
shell: bash
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
id: extract_branch
- name: Sanitize branch name for Docker tag
shell: bash
run: echo "sanitized_branch=$(echo '${{ steps.extract_branch.outputs.branch }}' | sed 's/\//-/g')" >> $GITHUB_OUTPUT
id: sanitize_branch
- name: Set SSH Agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
@@ -41,6 +45,9 @@ jobs:
ssh: |
default=${{ env.SSH_AUTH_SOCK }}
build-args: |
GIT_BRANCH=${{ steps.extract_branch.outputs.branch }}
"GIT_BRANCH=${{ steps.extract_branch.outputs.branch }}"
push: true
tags: s3docker.francissecada.com/pygentic_ai:${{ steps.extract_branch.outputs.branch }}.${{ steps.date.outputs.date }}
tags: |
s3docker.francissecada.com/pygentic_ai:${{ steps.sanitize_branch.outputs.sanitized_branch }}.${{ steps.date.outputs.date }}
s3docker.francissecada.com/pygentic_ai:${{ steps.sanitize_branch.outputs.sanitized_branch }}-latest
pull: true
+42
View File
@@ -0,0 +1,42 @@
name: Komodo Deployment Trigger
on:
workflow_run:
workflows: ["Docker Image CI"]
types:
- completed
branches: [ "**_deploy", "main" ]
jobs:
trigger_komodo_deployment:
runs-on: ubuntu-latest
# Only run if the 'Docker Image CI' workflow was successful
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Extract branch name from triggering workflow
id: branch
run: echo "BRANCH_NAME=$(echo ${{ github.event.workflow_run.head_branch }} | sed 's/\//-/g')" >> $GITHUB_OUTPUT
- name: Trigger Komodo Stack Deployment
env:
# The webhook URL is constructed from secrets
KOMODO_WEBHOOK_URL: "https://${{ secrets.KOMODO_HOST }}/listener/github/stack/${{ secrets.KOMODO_STACK_ID_OR_NAME }}/deploy"
run: |
# Get the branch name from the previous step's output
branch_name="${{ steps.branch.outputs.BRANCH_NAME }}"
# Construct a JSON payload containing the branch name
# This tells Komodo which specific branch/tag to deploy
payload=$(printf '{"ref":"%s"}' "$branch_name")
# Generate the signature based on the payload
signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "${{ secrets.KOMODO_WEBHOOK_SECRET }}" | sed 's/^.* //')
# Send the request to the Komodo webhook with the payload
echo "Triggering Pygentic-AI deployment for branch: $branch_name"
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=$signature" \
-d "$payload" \
--fail-with-body \
"${KOMODO_WEBHOOK_URL}"
+33 -23
View File
@@ -1,58 +1,68 @@
services:
web:
image: s3docker.francissecada.com/pygentic_ai:main.2024-11-30
image: s3docker.francissecada.com/pygentic_ai:${IMAGE_TAG:-main-latest}
container_name: pygentic_ai
restart: unless-stopped
deploy:
resources:
limits:
memory: 1024mb
memory: ${MEMORY_LIMIT:-1024mb}
reservations:
memory: ${MEMORY_RESERVATION:-512mb}
ports:
- "5051:5051"
- "0.0.0.0:${PORT:-5051}:${INTERNAL_PORT:-5051}"
env_file:
- ./stack.env
environment:
PORT: 5051
SERVER_ENV: prod
- PORT=${INTERNAL_PORT:-5051}
- SERVER_ENV=${SERVER_ENV:-prod}
volumes:
- ./src/proxy_urls.db:/opt/pygentic_ai/src/proxy_urls.db
command: bash -c "/opt/pygentic_ai/docker/pygentic_ai/python_start.sh"
healthcheck:
test: ["CMD", "curl", "-f", "-H", "Host: pygenticai.francissecada.com", "http://localhost:${INTERNAL_PORT:-5051}/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.pygentic_ai.entrypoints=websecure
- traefik.http.routers.pygentic_ai.rule=Host(`pygenticai.francissecada.com`)
- traefik.http.routers.pygentic_ai.tls=true
healthcheck:
test: curl --fail http://localhost:5051/ || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
- traefik.http.routers.pygentic_ai.entrypoints=websecure
- traefik.http.routers.pygentic_ai.tls.certresolver=letsencrypt
- traefik.http.services.pygentic_ai.loadbalancer.server.url=http://${DOCKER_HOST_IP:-192.168.99.85}:${PORT:-5051}
networks:
- proxy
celery_service:
image: s3docker.francissecada.com/pygentic_ai:main.2024-11-30
image: s3docker.francissecada.com/pygentic_ai:${IMAGE_TAG:-main-latest}
container_name: pygentic_ai_celery
restart: unless-stopped
deploy:
resources:
limits:
memory: 512mb
# build: .
command: ./docker/celery/start.sh
memory: ${CELERY_MEMORY_LIMIT:-512mb}
reservations:
memory: ${CELERY_MEMORY_RESERVATION:-256mb}
command: bash -c "/opt/pygentic_ai/docker/celery/start.sh"
env_file:
- ./stack.env
environment:
PORT: ${CELERY_PORT}
SERVER_ENV: staging
C_FORCE_ROOT: true
- PORT=${CELERY_PORT:-5052}
- SERVER_ENV=${SERVER_ENV:-prod}
- C_FORCE_ROOT=true
ports:
- "5052:5052"
- "0.0.0.0:${CELERY_PORT:-5052}:${CELERY_PORT:-5052}"
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.celery_pygentic_ai.entrypoints=websecure
- traefik.http.routers.celery_pygentic_ai.rule=Host(`celery.pygenticai.francissecada.com`)
- traefik.http.routers.celery_pygentic_ai.tls.certresolver=letsencrypt
networks:
- proxy
depends_on:
- web
networks:
proxy:
external: true