# Dokploy Deployment Guide

## Overview

This project uses **Gitea Actions** to build Docker images and **Dokploy** to deploy them. Each branch (dev, staging, main) has its own:
- Docker image tag
- Docker Compose file
- Dokploy application
- Domain

---

## Architecture

```
┌─────────────┐
│   Gitea     │
│  (Source)   │
└──────┬──────┘
       │ push event
       ↓
┌─────────────┐
│   Gitea     │
│   Actions   │ Builds Docker images
│  (CI/CD)    │ Tags: dev, staging, latest
└──────┬──────┘
       │
       ↓
┌─────────────┐
│   Gitea     │
│  Registry   │ git.app.flexinit.nl/oussamadouhou/ai-stack-deployer
└──────┬──────┘
       │ webhook (push event)
       ↓
┌─────────────┐
│   Dokploy   │ Pulls & deploys image
│  (Deploy)   │ Uses docker-compose.{env}.yml
└─────────────┘
```

---

## Branch Strategy

| Branch    | Image Tag | Compose File               | Domain (suggested)           |
|-----------|-----------|----------------------------|------------------------------|
| `dev`     | `dev`     | `docker-compose.dev.yml`     | portal-dev.ai.flexinit.nl    |
| `staging` | `staging` | `docker-compose.staging.yml` | portal-staging.ai.flexinit.nl |
| `main`    | `latest`  | `docker-compose.prod.yml`    | portal.ai.flexinit.nl        |

---

## Gitea Actions Workflow

**File**: `.gitea/workflows/docker-publish.yaml`

**Triggers**: Push to `dev`, `staging`, or `main` branches

**Builds**:
```yaml
dev branch      → git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:dev
staging branch  → git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:staging
main branch     → git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest
```

**Also creates SHA tags**: `{branch}-{short-sha}`

---

## Docker Compose Files

### `docker-compose.dev.yml`
- Pulls: `git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:dev`
- Environment: `NODE_ENV=development`
- Container name: `ai-stack-deployer-dev`

### `docker-compose.staging.yml`
- Pulls: `git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:staging`
- Environment: `NODE_ENV=staging`
- Container name: `ai-stack-deployer-staging`

### `docker-compose.prod.yml`
- Pulls: `git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest`
- Environment: `NODE_ENV=production`
- Container name: `ai-stack-deployer`

### `docker-compose.local.yml`
- **Builds locally** (doesn't pull from registry)
- For local development only
- Includes volume mounts for hot reload

---

## Shared Project Configuration (IMPORTANT)

### What is Shared Project Deployment?

The portal deploys **all user AI stacks as applications within a single shared Dokploy project**, instead of creating a new project for each user. This provides:

- ✅ Better organization (all stacks in one place)
- ✅ Shared environment variables
- ✅ Centralized monitoring
- ✅ Easier management

### How It Works

```
Dokploy Project: ai-stack-portal
├── Environment: deployments
│   ├── Application: john-dev
│   ├── Application: jane-prod
│   └── Application: alice-test
```

### Setting Up the Shared Project

**Step 1: Create the Shared Project in Dokploy**

1. In Dokploy UI, create a new project:
   - Name: `ai-stack-portal` (or any name you prefer)
   - Description: "Shared project for all user AI stacks"

2. Note the **Project ID** (visible in URL or API response)
   - Example: `2y2Glhz5Wy0dBNf6BOR_-`

3. Get the **Environment ID**:
   ```bash
   curl -s "http://10.100.0.20:3000/api/project.one?projectId=2y2Glhz5Wy0dBNf6BOR_-" \
     -H "Authorization: Bearer $DOKPLOY_API_TOKEN" | jq -r '.environments[0].id'
   ```
   - Example: `RqE9OFMdLwkzN7pif1xN8`

**Step 2: Configure Project-Level Variables**

In the shared project (`ai-stack-portal`), add these **project-level environment variables**:

| Variable Name | Value | Purpose |
|---------------|-------|---------|
| `SHARED_PROJECT_ID` | `2y2Glhz5Wy0dBNf6BOR_-` | The project where user stacks deploy |
| `SHARED_ENVIRONMENT_ID` | `RqE9OFMdLwkzN7pif1xN8` | The environment within that project |

**Step 3: Reference Variables in Portal Applications**

The portal's docker-compose files use Dokploy's variable syntax to reference these:

   ```yaml
   environment:
     - SHARED_PROJECT_ID=$${{project.SHARED_PROJECT_ID}}
     - SHARED_ENVIRONMENT_ID=$${{project.SHARED_ENVIRONMENT_ID}}
   ```

**This syntax `$${{project.VARIABLE}}` tells Dokploy**: "Get this value from the project-level environment variables"
   
**Note**: The double `$$` is required to escape the dollar sign in Docker Compose files.

### Important Notes

- ⚠️ **Both variables MUST be set** in the shared project for deployment to work
- ⚠️ If not set, portal will fall back to creating separate projects per user (legacy behavior)
- ✅ You can have different shared projects for dev/staging/prod environments
- ✅ All 3 portal deployments (dev/staging/prod) should point to their respective shared projects

---

## Setting Up Dokploy

### Step 1: Create Dev Application

1. **In Dokploy UI**, create new application:
   - **Name**: `ai-stack-deployer-dev`
   - **Type**: Docker Compose
   - **Repository**: `ssh://git@git.app.flexinit.nl:22222/oussamadouhou/ai-stack-deployer.git`
   - **Branch**: `dev`
   - **Compose File**: `docker-compose.dev.yml`

2. **Configure Domain**:
   - Add domain: `portal-dev.ai.flexinit.nl`
   - Enable SSL (via Traefik wildcard cert)

3. **Set Environment Variables**:
   
   **Important**: The portal application should be deployed **inside the shared project** (e.g., `ai-stack-portal-dev`).
   
   Then set these **project-level variables** in that shared project:
   ```env
   SHARED_PROJECT_ID=<your-shared-project-id>
   SHARED_ENVIRONMENT_ID=<your-shared-environment-id>
   ```
   
   And these **application-level variables** in the portal app:
   ```env
   DOKPLOY_URL=http://10.100.0.20:3000
   DOKPLOY_API_TOKEN=<your-token>
   STACK_DOMAIN_SUFFIX=ai.flexinit.nl
   STACK_IMAGE=git.app.flexinit.nl/flexinit/agent-stack:latest
   ```
   
   The docker-compose file will automatically reference the project-level variables using:
   ```yaml
   SHARED_PROJECT_ID=${{project.SHARED_PROJECT_ID}}
   SHARED_ENVIRONMENT_ID=${{project.SHARED_ENVIRONMENT_ID}}
   ```

4. **⚠️ CRITICAL: Configure Custom Docker Compose Command**:
   
   Because we use non-default compose file names (`docker-compose.dev.yml`, `docker-compose.prod.yml`, etc.), you **MUST** configure a custom command in Dokploy.
   
   **In Dokploy UI:**
   - Go to the application **Settings** or **Advanced** tab
   - Find **"Custom Command"** or **"Docker Compose Command"** field
   - Set it to:
     ```bash
     compose -p <app-name> -f ./docker-compose.dev.yml up -d --remove-orphans --pull always
     ```
   
   **Replace `<app-name>`** with your actual application name from Dokploy (e.g., `aistackportal-portal-0rohwx`)
   
   **Replace `docker-compose.dev.yml`** with the appropriate file for each environment:
   - Dev: `docker-compose.dev.yml`
   - Staging: `docker-compose.staging.yml`
   - Production: `docker-compose.prod.yml`
   
   **Why this is required:**
   - Dokploy's default command is `docker compose up -d` without the `-f` flag
   - Without `-f`, docker looks for `docker-compose.yml` (which doesn't exist)
   - This causes the error: `no configuration file provided: not found`
   
   **Full examples:**
   ```bash
   # Dev
   compose -p aistackportal-deployer-dev-xyz123 -f ./docker-compose.dev.yml up -d --remove-orphans --pull always
   
   # Staging  
   compose -p aistackportal-deployer-staging-abc456 -f ./docker-compose.staging.yml up -d --remove-orphans --pull always
   
   # Production
   compose -p aistackportal-portal-0rohwx -f ./docker-compose.prod.yml up -d --remove-orphans --pull always
   ```

5. **Configure Webhook**:
   - Event: **Push**
   - Branch: `dev`
   - This will auto-deploy when you push to dev branch

6. **Deploy**

### Step 2: Create Staging Application

Repeat Step 1 with these changes:
- **Name**: `ai-stack-deployer-staging`
- **Branch**: `staging`
- **Compose File**: `docker-compose.staging.yml`
- **Domain**: `portal-staging.ai.flexinit.nl`
- **Webhook Branch**: `staging`

### Step 3: Create Production Application

Repeat Step 1 with these changes:
- **Name**: `ai-stack-deployer-prod`
- **Branch**: `main`
- **Compose File**: `docker-compose.prod.yml`
- **Domain**: `portal.ai.flexinit.nl`
- **Webhook Branch**: `main`

---

## Deployment Workflow

### Development Cycle

```bash
# 1. Make changes on dev branch
git checkout dev
# ... make changes ...
git commit -m "feat: add new feature"
git push origin dev

# 2. Gitea Actions automatically builds dev image
# 3. Dokploy webhook triggers and deploys to portal-dev.ai.flexinit.nl

# 4. Test on dev environment
curl https://portal-dev.ai.flexinit.nl/health

# 5. When ready, merge to staging
git checkout staging
git merge dev
git push origin staging

# 6. Gitea Actions builds staging image
# 7. Dokploy deploys to portal-staging.ai.flexinit.nl

# 8. Final testing on staging, then merge to main
git checkout main
git merge staging
git push origin main

# 9. Gitea Actions builds production image (latest)
# 10. Dokploy deploys to portal.ai.flexinit.nl
```

---

## Image Tags Explained

Each push creates multiple tags:

### Example: Push to `dev` branch (commit `abc1234`)

Gitea Actions creates:
```
git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:dev       ← Latest dev
git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:dev-abc1234  ← Specific commit
```

### Example: Push to `main` branch (commit `xyz5678`)

Gitea Actions creates:
```
git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest    ← Latest production
git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:main-xyz5678 ← Specific commit
```

**Why?**
- Branch tags (`dev`, `staging`, `latest`) always point to latest build
- SHA tags allow you to rollback to specific commits if needed

---

## Rollback Strategy

### Quick Rollback in Dokploy

If a deployment breaks, you can quickly rollback:

1. **In Dokploy UI**, go to the application
2. **Edit** the docker-compose file
3. Change the image tag to a previous SHA:
   ```yaml
   image: git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:main-abc1234
   ```
4. **Redeploy**

### Manual Rollback via Git

```bash
# Find the last working commit
git log --oneline

# Revert to that commit
git revert HEAD  # or git reset --hard <commit-sha>

# Push to trigger rebuild
git push origin main
```

---

## Local Development

### Using docker-compose.local.yml

```bash
# Build and run locally
docker-compose -f docker-compose.local.yml up -d

# View logs
docker-compose -f docker-compose.local.yml logs -f

# Stop
docker-compose -f docker-compose.local.yml down
```

### Using Bun directly (without Docker)

```bash
# Install dependencies
bun install

# Run dev server (API + Vite)
bun run dev

# Run API only
bun run dev:api

# Run client only
bun run dev:client
```

---

## Environment Variables

### Required in Dokploy

```env
DOKPLOY_URL=http://10.100.0.20:3000
DOKPLOY_API_TOKEN=<your-token>
```

### Optional (with defaults)

```env
PORT=3000
HOST=0.0.0.0
STACK_DOMAIN_SUFFIX=ai.flexinit.nl
STACK_IMAGE=git.app.flexinit.nl/flexinit/agent-stack:latest
RESERVED_NAMES=admin,api,www,root,system,test,demo,portal
```

### Per-Environment Overrides

If dev/staging/prod need different configs, set them in Dokploy:

**Dev**:
```env
STACK_DOMAIN_SUFFIX=dev-ai.flexinit.nl
```

**Staging**:
```env
STACK_DOMAIN_SUFFIX=staging-ai.flexinit.nl
```

**Prod**:
```env
STACK_DOMAIN_SUFFIX=ai.flexinit.nl
```

---

## Troubleshooting

### Build Fails in Gitea Actions

Check the workflow logs in Gitea:
```
https://git.app.flexinit.nl/oussamadouhou/ai-stack-deployer/actions
```

Common issues:
- **AVX error**: Fixed in Dockerfile (uses Node.js for build)
- **Registry auth**: Check `REGISTRY_TOKEN` secret in Gitea

### Deployment Fails in Dokploy

1. **Check Dokploy logs**: Application → Logs
2. **Verify image exists**:
   ```bash
   docker pull git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:dev
   ```
3. **Check environment variables**: Make sure all required vars are set

### Error: "no configuration file provided: not found"

**Symptom:**
```
╔══════════════════════════════════════════════════════════════════════════════╗
║ Command: docker compose up -d --force-recreate --pull always                 ║
╚══════════════════════════════════════════════════════════════════════════════╝
no configuration file provided: not found
Error: ❌ Docker command failed
```

**Cause:** Dokploy is looking for the default `docker-compose.yml` file, which doesn't exist. We use environment-specific files (`docker-compose.dev.yml`, `docker-compose.prod.yml`, etc.).

**Solution:** Configure a **custom Docker Compose command** in Dokploy:

1. Go to your application in Dokploy UI
2. Navigate to **Settings** → **Advanced** (or similar section)
3. Find **"Custom Command"** field
4. Set it to:
   ```bash
   compose -p <app-name> -f ./docker-compose.{env}.yml up -d --remove-orphans --pull always
   ```
   
   Replace:
   - `<app-name>` with your actual Dokploy app name (e.g., `aistackportal-portal-0rohwx`)
   - `{env}` with `dev`, `staging`, or `prod`
   
   **Example for production:**
   ```bash
   compose -p aistackportal-portal-0rohwx -f ./docker-compose.prod.yml up -d --remove-orphans --pull always
   ```

5. Save and redeploy

**Why the `-f` flag is needed:** Docker Compose defaults to looking for `docker-compose.yml`. The `-f` flag explicitly specifies which file to use.

### Health Check Failing

```bash
# SSH into Dokploy host
ssh user@10.100.0.20

# Check container logs
docker logs ai-stack-deployer-dev

# Test health endpoint
curl http://localhost:3000/health
```

### Webhook Not Triggering

1. **In Dokploy**, check webhook configuration
2. **In Gitea**, go to repo Settings → Webhooks
3. Verify webhook URL and secret match
4. Check recent deliveries for errors

---

## Production Considerations

### 1. Image Size Optimization

The Docker image excludes dev files via `.dockerignore`:
- ✅ `docs/` - excluded
- ✅ `scripts/` - excluded
- ✅ `.gitea/` - excluded
- ✅ `*.md` (except README.md) - excluded

Current image size: ~150MB

### 2. Security

- Container runs as non-root user (`nodejs:1001`)
- No secrets in source code (uses `.env`)
- Dokploy API accessible only on internal network

### 3. Monitoring

Set up alerts for:
- Container health check failures
- Memory/CPU usage spikes
- Deployment failures

### 4. Backup Strategy

- **Database**: This app has no database (stateless)
- **Configuration**: Environment variables stored in Dokploy (backed up)
- **Code**: Stored in Gitea (backed up)

---

## Summary

| Environment | Domain                       | Image Tag | Auto-Deploy? |
|-------------|------------------------------|-----------|--------------|
| Dev         | portal-dev.ai.flexinit.nl    | `dev`     | ✅ On push   |
| Staging     | portal-staging.ai.flexinit.nl | `staging` | ✅ On push   |
| Production  | portal.ai.flexinit.nl        | `latest`  | ✅ On push   |

**Next Steps**:
1. ✅ Push changes to `dev` branch
2. ⏳ Create 3 Dokploy applications (dev, staging, prod)
3. ⏳ Configure webhooks for each branch
4. ⏳ Deploy and test each environment

---

**Questions?** Check the main README.md or CLAUDE.md for more details.
