Files
ai-stack-deployer/docs/SHARED_PROJECT_DEPLOYMENT.md
Oussama Douhou 9a593b8b7c feat: add shared project deployment with Dokploy project-level variables
- Add SHARED_PROJECT_ID and SHARED_ENVIRONMENT_ID to all docker-compose files
- Use Dokploy's project-level variable syntax: ${{project.VARIABLE}}
- Deploy all user AI stacks to a single shared Dokploy project
- Update DOKPLOY_DEPLOYMENT.md with shared project configuration guide
- Add comprehensive SHARED_PROJECT_DEPLOYMENT.md architecture documentation

Benefits:
- Centralized management (all stacks in one project)
- Resource efficiency (no per-user project overhead)
- Simplified configuration (project-level shared vars)
- Better organization (500 apps in 1 project vs 500 projects)

How it works:
1. Portal reads SHARED_PROJECT_ID from environment
2. Docker-compose uses ${{project.SHARED_PROJECT_ID}} to reference project-level vars
3. Dokploy resolves these at runtime
4. Portal deploys user stacks as applications within the shared project

Fallback: If variables not set, falls back to legacy behavior (separate project per user)
2026-01-13 12:01:59 +01:00

10 KiB

Shared Project Deployment Architecture

Overview

The AI Stack Deployer portal deploys all user AI stacks to a single shared Dokploy project instead of creating a new project for each user.


Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│  Dokploy: ai-stack-portal (Shared Project)                     │
│  ID: 2y2Glhz5Wy0dBNf6BOR_-                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  📦 Portal Application: ai-stack-deployer-prod                 │
│     ├─ Domain: portal.ai.flexinit.nl                           │
│     ├─ Image: git.app.flexinit.nl/.../ai-stack-deployer:latest│
│     └─ Env: SHARED_PROJECT_ID=${{project.SHARED_PROJECT_ID}}  │
│                                                                 │
│  ───────────────────────────────────────────────────────────── │
│                                                                 │
│  📦 User Stack: john-dev                                        │
│     ├─ Domain: john-dev.ai.flexinit.nl                         │
│     ├─ Image: git.app.flexinit.nl/.../agent-stack:latest      │
│     └─ Deployed by: Portal                                     │
│                                                                 │
│  📦 User Stack: jane-prod                                       │
│     ├─ Domain: jane-prod.ai.flexinit.nl                        │
│     ├─ Image: git.app.flexinit.nl/.../agent-stack:latest      │
│     └─ Deployed by: Portal                                     │
│                                                                 │
│  📦 User Stack: alice-test                                      │
│     ├─ Domain: alice-test.ai.flexinit.nl                       │
│     ├─ Image: git.app.flexinit.nl/.../agent-stack:latest      │
│     └─ Deployed by: Portal                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

How It Works

Step 1: Portal Reads Configuration

When a user submits a stack name (e.g., "john-dev"), the portal:

  1. Reads environment variables:

    const sharedProjectId = process.env.SHARED_PROJECT_ID;
    const sharedEnvironmentId = process.env.SHARED_ENVIRONMENT_ID;
    
  2. These are set via Dokploy's project-level variables:

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

Step 2: Portal Deploys to Shared Project

Instead of creating a new project, the portal:

// OLD BEHAVIOR (legacy):
// createProject(`ai-stack-${username}`)  ❌ Creates new project per user

// NEW BEHAVIOR (current):
// Uses existing shared project ID  ✅
const projectId = sharedProjectId;  // From environment variable
const environmentId = sharedEnvironmentId;

// Creates application IN the shared project
createApplication({
  projectId: projectId,
  environmentId: environmentId,
  name: `${username}-stack`,
  image: 'git.app.flexinit.nl/.../agent-stack:latest',
  domain: `${username}.ai.flexinit.nl`
});

Step 3: User Accesses Their Stack

User visits https://john-dev.ai.flexinit.nl → Traefik routes to their application inside the shared project.


Configuration Steps

1. Create Shared Project in Dokploy

  1. In Dokploy UI, create project:

    • Name: ai-stack-portal
    • Description: "Shared project for all user AI stacks"
  2. Get the Project ID:

    # Via API
    curl -s "http://10.100.0.20:3000/api/project.all" \
      -H "Authorization: Bearer $DOKPLOY_API_TOKEN" | \
      jq -r '.[] | select(.name=="ai-stack-portal") | .id'
    
    # Output: 2y2Glhz5Wy0dBNf6BOR_-
    
  3. Get the Environment ID:

    curl -s "http://10.100.0.20:3000/api/project.one?projectId=2y2Glhz5Wy0dBNf6BOR_-" \
      -H "Authorization: Bearer $DOKPLOY_API_TOKEN" | \
      jq -r '.environments[0].id'
    
    # Output: RqE9OFMdLwkzN7pif1xN8
    

2. Set Project-Level Variables

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

Variable Value Example
SHARED_PROJECT_ID Your project ID 2y2Glhz5Wy0dBNf6BOR_-
SHARED_ENVIRONMENT_ID Your environment ID RqE9OFMdLwkzN7pif1xN8

How to set in Dokploy UI:

  • Go to Project → Settings → Environment Variables
  • Add variables at project level (not application level)

3. Deploy Portal Application

Deploy the portal inside the same shared project:

  1. Application Details:

    • Name: ai-stack-deployer-prod
    • Type: Docker Compose
    • Compose File: docker-compose.prod.yml
    • Branch: main
  2. The docker-compose file automatically references project variables:

    environment:
      - SHARED_PROJECT_ID=${{project.SHARED_PROJECT_ID}}  # ← Magic happens here
      - SHARED_ENVIRONMENT_ID=${{project.SHARED_ENVIRONMENT_ID}}
    
  3. Dokploy resolves ${{project.VAR}} to the actual value from project-level variables.


Benefits

Centralized Management

All user stacks in one place:

  • Easy to list all active stacks
  • Shared monitoring dashboard
  • Centralized logging

Resource Efficiency

  • No overhead of separate projects per user
  • Shared network and resources
  • Easier to manage quotas

Simplified Configuration

  • Project-level environment variables shared by all stacks
  • Single source of truth for common configs
  • Easy to update STACK_IMAGE for all users

Better Organization

Projects in Dokploy:
├── ai-stack-portal (500 user applications)  ✅ Clean
└── NOT:
    ├── ai-stack-john
    ├── ai-stack-jane
    ├── ai-stack-alice
    └── ... (500 separate projects)  ❌ Messy

Fallback Behavior

If SHARED_PROJECT_ID and SHARED_ENVIRONMENT_ID are not set, the portal falls back to legacy behavior:

// Code in src/orchestrator/production-deployer.ts (lines 187-196)
const sharedProjectId = config.sharedProjectId || process.env.SHARED_PROJECT_ID;
const sharedEnvironmentId = config.sharedEnvironmentId || process.env.SHARED_ENVIRONMENT_ID;

if (sharedProjectId && sharedEnvironmentId) {
  // Use shared project ✅
  state.resources.projectId = sharedProjectId;
  state.resources.environmentId = sharedEnvironmentId;
  return;
}

// Fallback: Create separate project per user ⚠️
const projectName = `ai-stack-${config.stackName}`;
const existingProject = await this.client.findProjectByName(projectName);
// ...

This ensures backwards compatibility but is not recommended.


Troubleshooting

Portal Creates Separate Projects Instead of Using Shared Project

Cause: SHARED_PROJECT_ID or SHARED_ENVIRONMENT_ID not set.

Solution:

  1. Check project-level variables in Dokploy:

    curl -s "http://10.100.0.20:3000/api/project.one?projectId=YOUR_PROJECT_ID" \
      -H "Authorization: Bearer $DOKPLOY_API_TOKEN" | \
      jq '.environmentVariables'
    
  2. Ensure the portal application's docker-compose references them:

    environment:
      - SHARED_PROJECT_ID=${{project.SHARED_PROJECT_ID}}
      - SHARED_ENVIRONMENT_ID=${{project.SHARED_ENVIRONMENT_ID}}
    
  3. Redeploy the portal application.

Variable Reference Not Working

Symptom: Portal logs show undefined for SHARED_PROJECT_ID.

Cause: Using wrong syntax.

Correct syntax:

- SHARED_PROJECT_ID=${{project.SHARED_PROJECT_ID}}  ✅

Wrong syntax:

- SHARED_PROJECT_ID=${SHARED_PROJECT_ID}  ❌ (shell substitution, not Dokploy)
- SHARED_PROJECT_ID={{project.SHARED_PROJECT_ID}}  ❌ (missing $)

How to Verify Configuration

Check portal container environment:

# SSH into Dokploy host
ssh user@10.100.0.20

# Inspect portal container
docker exec ai-stack-deployer env | grep SHARED

# Should show:
SHARED_PROJECT_ID=2y2Glhz5Wy0dBNf6BOR_-
SHARED_ENVIRONMENT_ID=RqE9OFMdLwkzN7pif1xN8

Environment-Specific Shared Projects

You can have separate shared projects for dev/staging/prod:

Portal Environment Shared Project Purpose
Dev ai-stack-portal-dev Development user stacks
Staging ai-stack-portal-staging Staging user stacks
Prod ai-stack-portal Production user stacks

Each portal deployment references its own shared project:

  • portal-dev.ai.flexinit.nlai-stack-portal-dev
  • portal-staging.ai.flexinit.nlai-stack-portal-staging
  • portal.ai.flexinit.nlai-stack-portal

Migration from Legacy

If you're currently using the legacy behavior (separate projects per user):

Option 1: Gradual Migration

  • New deployments use shared project
  • Old deployments remain in separate projects
  • Migrate old stacks manually over time

Option 2: Full Migration

  1. Create shared project
  2. Set project-level variables
  3. Redeploy all user stacks to shared project
  4. Delete old separate projects

Note: Migration requires downtime for each stack being moved.


Reference

  • Environment Variable Syntax: See Dokploy docs on project-level variables
  • Code Location: src/orchestrator/production-deployer.ts (lines 178-200)
  • Example IDs: .env.example (lines 25-27)

Questions? Check the main deployment guide: DOKPLOY_DEPLOYMENT.md