Docker Compose interprets $ as variable substitution, so we need to escape
Dokploy's project-level variable syntax by doubling the dollar sign.
Changes:
- docker-compose.*.yml: ${{project.VAR}} → $${{project.VAR}}
- Updated DOKPLOY_DEPLOYMENT.md with correct syntax and explanation
- Updated SHARED_PROJECT_DEPLOYMENT.md with correct syntax and explanation
This fixes the 'You may need to escape any $ with another $' error when
deploying via Dokploy.
Evidence: Tested in Dokploy deployment - error resolved with $$ escaping.
489 lines
12 KiB
Markdown
489 lines
12 KiB
Markdown
# 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. **Configure Webhook**:
|
|
- Event: **Push**
|
|
- Branch: `dev`
|
|
- This will auto-deploy when you push to dev branch
|
|
|
|
5. **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
|
|
|
|
### 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.
|