Skip to content
Closed
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
156 changes: 156 additions & 0 deletions .github/workflows/deploy-openhands.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: Deploy OpenHands Agents

on:
push:
branches: [openhands-migration, master]
paths:
- 'vibeteam/**'
- 'k8s/openhands/**'
- 'Dockerfile'
- 'pyproject.toml'
- '.github/workflows/deploy-openhands.yml'
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'dev'
type: choice
options:
- dev
- prod

env:
REGISTRY: ghcr.io
IMAGE_NAME: vibetechnologies/vibeteam

jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

outputs:
image_tag: ${{ steps.meta.outputs.version }}

steps:
- name: Checkout
uses: actions/checkout@v4

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

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

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
name: Deploy to K3s
needs: build
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'dev' }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'

- name: Configure kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config
chmod 600 ~/.kube/config

- name: Verify cluster access
run: kubectl cluster-info

- name: Create namespace
run: |
kubectl create namespace openhands-ops --dry-run=client -o yaml | kubectl apply -f -

- name: Create image pull secret
run: |
kubectl -n openhands-ops delete secret ghcr-login --ignore-not-found
kubectl -n openhands-ops create secret docker-registry ghcr-login \
--docker-server=ghcr.io \
--docker-username=${{ github.actor }} \
--docker-password=${{ secrets.PAT_TOKEN }}

- name: Create secrets
run: |
kubectl -n openhands-ops delete secret vibeteam-secrets --ignore-not-found
kubectl -n openhands-ops create secret generic vibeteam-secrets \
--from-literal=LLM_API_KEY="${{ secrets.LLM_API_KEY }}" \
--from-literal=LLM_MODEL="${{ secrets.LLM_MODEL || 'azure/gpt-5-2' }}" \
--from-literal=LLM_BASE_URL="${{ secrets.LLM_BASE_URL }}" \
--from-literal=GITHUB_TOKEN="${{ secrets.PAT_TOKEN }}" \
--from-literal=SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}" \
--from-literal=LANGFUSE_PUBLIC_KEY="${{ secrets.LANGFUSE_PUBLIC_KEY }}" \
--from-literal=LANGFUSE_SECRET_KEY="${{ secrets.LANGFUSE_SECRET_KEY }}"

- name: Update image tag
run: |
cd k8s/openhands
# Update the image tag in kustomization.yaml
sed -i "s/newTag: .*/newTag: ${{ needs.build.outputs.image_tag }}/" kustomization.yaml

- name: Deploy with Kustomize
run: |
kubectl apply -k k8s/openhands

- name: Wait for rollout
run: |
kubectl -n openhands-ops rollout status deployment/vibeteam-agents --timeout=300s

- name: Verify deployment
run: |
echo "=== Deployment Status ==="
kubectl -n openhands-ops get deployment vibeteam-agents
echo ""
echo "=== Pods ==="
kubectl -n openhands-ops get pods -l app=vibeteam-agents
echo ""
echo "=== Service ==="
kubectl -n openhands-ops get service vibeteam-agents
echo ""
echo "=== IngressRoute ==="
kubectl -n openhands-ops get ingressroute vibeteam-agents || echo "IngressRoute not yet created"

- name: Test health endpoint
run: |
# Get the pod name
POD=$(kubectl -n openhands-ops get pods -l app=vibeteam-agents -o jsonpath='{.items[0].metadata.name}')
# Port forward and test (with timeout)
kubectl -n openhands-ops port-forward $POD 8000:8000 &
sleep 5
curl -sf http://localhost:8000/health || echo "Health check failed"
kill %1 2>/dev/null || true
66 changes: 66 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# VibeTeam OpenHands Agent Server
#
# Multi-stage build for the FastAPI webhook server.
# This image runs alongside a Docker-in-Docker sidecar in K8s.

# Stage 1: Build dependencies
FROM python:3.12-slim as builder

WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
git \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY pyproject.toml .
RUN pip install --no-cache-dir build && \
pip wheel --no-cache-dir --wheel-dir /wheels -e .

# Stage 2: Runtime image
FROM python:3.12-slim

WORKDIR /app

# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
docker.io \
&& rm -rf /var/lib/apt/lists/*

# Install GitHub CLI
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \
tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \
apt-get update && apt-get install -y gh && \
rm -rf /var/lib/apt/lists/*

# Copy wheels and install
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*.whl && rm -rf /wheels

# Copy application code
COPY vibeteam/ /app/vibeteam/

# Create workspace directory
RUN mkdir -p /workspace

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PORT=8000

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1

# Run the webhook server
CMD ["python", "-m", "uvicorn", "vibeteam.webhooks.server:app", "--host", "0.0.0.0", "--port", "8000"]
145 changes: 145 additions & 0 deletions k8s/openhands/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# VibeTeam OpenHands Agent Deployment
#
# This deploys the FastAPI webhook server with a Docker-in-Docker sidecar
# for running OpenHands agents in isolated containers.
apiVersion: apps/v1
kind: Deployment
metadata:
name: vibeteam-agents
labels:
app: vibeteam-agents
spec:
replicas: 1
selector:
matchLabels:
app: vibeteam-agents
template:
metadata:
labels:
app: vibeteam-agents
spec:
serviceAccountName: vibeteam-agents
imagePullSecrets:
- name: ghcr-login
containers:
# Main application - FastAPI webhook server
- name: webhook-server
image: vibeteam
imagePullPolicy: Always
ports:
- containerPort: 8000
name: http
env:
# LLM Configuration
- name: LLM_API_KEY
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: LLM_API_KEY
- name: LLM_MODEL
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: LLM_MODEL
- name: LLM_BASE_URL
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: LLM_BASE_URL
# GitHub
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: GITHUB_TOKEN
- name: GITHUB_WEBHOOK_SECRET
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: GITHUB_WEBHOOK_SECRET
optional: true
# Sentry
- name: SENTRY_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: SENTRY_AUTH_TOKEN
- name: SENTRY_WEBHOOK_SECRET
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: SENTRY_WEBHOOK_SECRET
optional: true
# Langfuse
- name: LANGFUSE_PUBLIC_KEY
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: LANGFUSE_PUBLIC_KEY
- name: LANGFUSE_SECRET_KEY
valueFrom:
secretKeyRef:
name: vibeteam-secrets
key: LANGFUSE_SECRET_KEY
- name: LANGFUSE_BASE_URL
value: "https://langfuse.vibebrowser.app"
# Docker host (DinD sidecar)
- name: DOCKER_HOST
value: "tcp://localhost:2375"
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: workspace
mountPath: /workspace

# Docker-in-Docker sidecar for OpenHands agents
- name: dind
image: docker:24-dind
securityContext:
privileged: true
env:
- name: DOCKER_TLS_CERTDIR
value: "" # Disable TLS for localhost
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "2Gi"
cpu: "1"
volumeMounts:
- name: dind-storage
mountPath: /var/lib/docker
- name: workspace
mountPath: /workspace

volumes:
- name: dind-storage
emptyDir: {}
- name: workspace
emptyDir:
sizeLimit: 10Gi

---
# Service Account for the agents
apiVersion: v1
kind: ServiceAccount
metadata:
name: vibeteam-agents
27 changes: 27 additions & 0 deletions k8s/openhands/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Traefik IngressRoute for agents.vibebrowser.app
# Uses the existing Traefik installation on the K3s cluster
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: vibeteam-agents
spec:
entryPoints:
- websecure
routes:
- match: Host(`agents.vibebrowser.app`)
kind: Rule
services:
- name: vibeteam-agents
port: 80
tls:
certResolver: letsencrypt
---
# Middleware for rate limiting
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: ratelimit
spec:
rateLimit:
average: 100
burst: 50
Loading
Loading