Files
ai-stack-deployer/scripts/deploy-to-dokploy.sh
Oussama Douhou 19845880e3 fix(ci): trigger workflow on main branch to enable :latest tag
Changes:
- Create Gitea workflow for ai-stack-deployer
- Trigger on main branch (default branch)
- Use oussamadouhou + REGISTRY_TOKEN for authentication
- Build from ./Dockerfile

This enables :latest tag creation via {{is_default_branch}}.

Tags created:
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:<sha>

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 23:33:39 +01:00

387 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# AI Stack Deployer - Automated Deployment Script
# Deploys the AI Stack Deployer application to Dokploy
#
# Usage: ./scripts/deploy-to-dokploy.sh [options]
# Options:
# --registry REGISTRY Docker registry to push to (default: local Docker)
# --domain DOMAIN Domain name for deployment (default: portal.ai.flexinit.nl)
# --project-name NAME Dokploy project name (default: ai-stack-deployer-portal)
# --skip-build Skip Docker build (use existing image)
# --skip-test Skip local testing
#
set -euo pipefail
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="${SCRIPT_DIR}/.."
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
LOG_FILE="${PROJECT_ROOT}/deployment-${TIMESTAMP}.log"
# Default values
REGISTRY=""
DOMAIN="portal.ai.flexinit.nl"
PROJECT_NAME="ai-stack-deployer-portal"
APP_NAME="ai-stack-deployer-web"
IMAGE_NAME="ai-stack-deployer"
SKIP_BUILD=false
SKIP_TEST=false
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $*" | tee -a "${LOG_FILE}"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "${LOG_FILE}"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" | tee -a "${LOG_FILE}"
}
log_step() {
echo -e "\n${GREEN}==>${NC} $*\n" | tee -a "${LOG_FILE}"
}
# Error handler
error_exit() {
log_error "$1"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--registry)
REGISTRY="$2"
shift 2
;;
--domain)
DOMAIN="$2"
shift 2
;;
--project-name)
PROJECT_NAME="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=true
shift
;;
--skip-test)
SKIP_TEST=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
log_info "Deployment started at ${TIMESTAMP}"
log_info "Target domain: ${DOMAIN}"
log_info "Project name: ${PROJECT_NAME}"
cd "${PROJECT_ROOT}"
# Load environment variables
if [ ! -f .env ]; then
error_exit ".env file not found. Please create it from .env.example"
fi
source .env
if [ -z "${DOKPLOY_API_TOKEN}" ]; then
error_exit "DOKPLOY_API_TOKEN not set in .env"
fi
if [ -z "${DOKPLOY_URL}" ]; then
error_exit "DOKPLOY_URL not set in .env"
fi
# Phase 1: Pre-deployment checks
log_step "Phase 1: Pre-deployment Verification"
log_info "Checking Dokploy API connectivity..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
"${DOKPLOY_URL}/api/project.all" || echo "000")
if [ "${HTTP_CODE}" != "200" ]; then
error_exit "Dokploy API unreachable or unauthorized (HTTP ${HTTP_CODE})"
fi
log_info "✓ Dokploy API accessible"
# Check Docker
if ! command -v docker &> /dev/null; then
error_exit "Docker not installed"
fi
# Try docker with sg if needed
if ! docker ps &> /dev/null; then
if sg docker -c "docker ps" &> /dev/null; then
log_info "Using 'sg docker' for Docker commands"
DOCKER_CMD="sg docker -c"
else
error_exit "Docker not accessible. Check permissions."
fi
else
DOCKER_CMD=""
fi
log_info "✓ Docker accessible"
# Phase 2: Build Docker image
if [ "${SKIP_BUILD}" = false ]; then
log_step "Phase 2: Building Docker Image"
log_info "Building image: ${IMAGE_NAME}:${TIMESTAMP}"
${DOCKER_CMD} docker build \
-t "${IMAGE_NAME}:${TIMESTAMP}" \
-t "${IMAGE_NAME}:latest" \
. 2>&1 | tee -a "${LOG_FILE}" || error_exit "Docker build failed"
# Get image info
IMAGE_ID=$(${DOCKER_CMD} docker images -q "${IMAGE_NAME}:latest")
IMAGE_SIZE=$(${DOCKER_CMD} docker images "${IMAGE_NAME}:latest" --format "{{.Size}}")
log_info "✓ Image built successfully"
log_info " Image ID: ${IMAGE_ID}"
log_info " Size: ${IMAGE_SIZE}"
else
log_warn "Skipping build (--skip-build specified)"
fi
# Phase 3: Local testing
if [ "${SKIP_TEST}" = false ]; then
log_step "Phase 3: Local Container Testing"
log_info "Starting test container..."
# Stop existing test container if any
${DOCKER_CMD} docker stop ai-stack-deployer-test 2>/dev/null || true
${DOCKER_CMD} docker rm ai-stack-deployer-test 2>/dev/null || true
# Start test container
${DOCKER_CMD} docker run -d \
--name ai-stack-deployer-test \
-p 3001:3000 \
--env-file .env \
"${IMAGE_NAME}:latest" || error_exit "Failed to start test container"
log_info "Waiting for container to be healthy..."
sleep 5
# Test health endpoint
if ! curl -sf http://localhost:3001/health > /dev/null; then
${DOCKER_CMD} docker logs ai-stack-deployer-test | tail -20
${DOCKER_CMD} docker stop ai-stack-deployer-test
${DOCKER_CMD} docker rm ai-stack-deployer-test
error_exit "Health check failed"
fi
log_info "✓ Container healthy"
# Cleanup
${DOCKER_CMD} docker stop ai-stack-deployer-test
${DOCKER_CMD} docker rm ai-stack-deployer-test
log_info "✓ Local testing complete"
else
log_warn "Skipping local test (--skip-test specified)"
fi
# Phase 4: Registry push (if specified)
if [ -n "${REGISTRY}" ]; then
log_step "Phase 4: Pushing to Registry"
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:latest"
log_info "Tagging image for registry: ${FULL_IMAGE_NAME}"
${DOCKER_CMD} docker tag "${IMAGE_NAME}:latest" "${FULL_IMAGE_NAME}"
log_info "Pushing to registry..."
${DOCKER_CMD} docker push "${FULL_IMAGE_NAME}" || error_exit "Failed to push to registry"
log_info "✓ Image pushed to registry"
IMAGE_FOR_DOKPLOY="${FULL_IMAGE_NAME}"
else
log_warn "No registry specified - Dokploy must have access to local Docker"
IMAGE_FOR_DOKPLOY="${IMAGE_NAME}:latest"
fi
# Phase 5: Deploy to Dokploy
log_step "Phase 5: Deploying to Dokploy"
# Check if project exists
log_info "Checking for existing project..."
EXISTING_PROJECT=$(curl -s \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
"${DOKPLOY_URL}/api/project.all" | \
jq -r ".[]? | select(.name==\"${PROJECT_NAME}\") | .projectId" || echo "")
if [ -n "${EXISTING_PROJECT}" ]; then
log_info "✓ Found existing project: ${EXISTING_PROJECT}"
PROJECT_ID="${EXISTING_PROJECT}"
else
log_info "Creating new project..."
CREATE_PROJECT_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/project.create" \
-d "{
\"name\": \"${PROJECT_NAME}\",
\"description\": \"Self-service portal for deploying AI stacks\"
}") || error_exit "Failed to create project"
PROJECT_ID=$(echo "${CREATE_PROJECT_RESPONSE}" | jq -r '.project.projectId // empty')
if [ -z "${PROJECT_ID}" ]; then
log_error "API Response: ${CREATE_PROJECT_RESPONSE}"
error_exit "Failed to extract project ID"
fi
log_info "✓ Created project: ${PROJECT_ID}"
fi
# Create/Update application
log_info "Creating application..."
# Prepare environment variables
ENV_VARS="DOKPLOY_URL=${DOKPLOY_URL}
DOKPLOY_API_TOKEN=${DOKPLOY_API_TOKEN}
PORT=3000
HOST=0.0.0.0
STACK_DOMAIN_SUFFIX=${STACK_DOMAIN_SUFFIX:-ai.flexinit.nl}
STACK_IMAGE=${STACK_IMAGE:-git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest}
RESERVED_NAMES=${RESERVED_NAMES:-admin,api,www,root,system,test,demo,portal}"
CREATE_APP_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/application.create" \
-d "{
\"name\": \"${APP_NAME}\",
\"projectId\": \"${PROJECT_ID}\",
\"dockerImage\": \"${IMAGE_FOR_DOKPLOY}\",
\"env\": \"${ENV_VARS}\"
}") || error_exit "Failed to create application"
APP_ID=$(echo "${CREATE_APP_RESPONSE}" | jq -r '.application.applicationId // .applicationId // empty')
if [ -z "${APP_ID}" ]; then
log_error "API Response: ${CREATE_APP_RESPONSE}"
error_exit "Failed to extract application ID"
fi
log_info "✓ Created application: ${APP_ID}"
# Configure domain
log_info "Configuring domain: ${DOMAIN}"
curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/domain.create" \
-d "{
\"domain\": \"${DOMAIN}\",
\"applicationId\": \"${APP_ID}\",
\"https\": true,
\"port\": 3000
}" || log_warn "Domain configuration may have failed (might already exist)"
log_info "✓ Domain configured"
# Deploy application
log_info "Triggering deployment..."
DEPLOY_RESPONSE=$(curl -s -X POST \
-H "x-api-key: ${DOKPLOY_API_TOKEN}" \
-H "Content-Type: application/json" \
"${DOKPLOY_URL}/api/application.deploy" \
-d "{
\"applicationId\": \"${APP_ID}\"
}") || error_exit "Failed to trigger deployment"
DEPLOY_ID=$(echo "${DEPLOY_RESPONSE}" | jq -r '.deploymentId // "unknown"')
log_info "✓ Deployment triggered: ${DEPLOY_ID}"
log_info "Monitor at: ${DOKPLOY_URL}/project/${PROJECT_ID}"
# Phase 6: Verification
log_step "Phase 6: Deployment Verification"
log_info "Waiting for deployment (60 seconds)..."
sleep 60
log_info "Testing health endpoint..."
if curl -sf "https://${DOMAIN}/health" > /dev/null 2>&1; then
log_info "✓ Application is healthy at https://${DOMAIN}"
# Get health status
HEALTH_RESPONSE=$(curl -s "https://${DOMAIN}/health")
echo "${HEALTH_RESPONSE}" | jq . || echo "${HEALTH_RESPONSE}"
else
log_warn "Health check failed - deployment may still be in progress"
log_info "Check status at: ${DOKPLOY_URL}/project/${PROJECT_ID}"
fi
# Save deployment record
log_step "Deployment Summary"
cat > "deployment-record-${TIMESTAMP}.txt" << EOF
Deployment Completed: $(date -Iseconds)
===========================================
Configuration:
- Project Name: ${PROJECT_NAME}
- Application Name: ${APP_NAME}
- Domain: https://${DOMAIN}
- Image: ${IMAGE_FOR_DOKPLOY}
Dokploy IDs:
- Project ID: ${PROJECT_ID}
- Application ID: ${APP_ID}
- Deployment ID: ${DEPLOY_ID}
Management:
- Dokploy Console: ${DOKPLOY_URL}/project/${PROJECT_ID}
- Application URL: https://${DOMAIN}
- Health Check: https://${DOMAIN}/health
Build Info:
- Timestamp: ${TIMESTAMP}
- Image ID: ${IMAGE_ID:-N/A}
- Image Size: ${IMAGE_SIZE:-N/A}
Status: SUCCESS
EOF
log_info "Deployment record saved: deployment-record-${TIMESTAMP}.txt"
log_info "Deployment log saved: ${LOG_FILE}"
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Deployment Completed Successfully!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e " 🌐 Application URL: ${GREEN}https://${DOMAIN}${NC}"
echo -e " ⚙️ Dokploy Console: ${DOKPLOY_URL}/project/${PROJECT_ID}"
echo -e " 📊 Health Check: https://${DOMAIN}/health"
echo ""
exit 0