Skip to content

Comments

Encapsulate Deployments in Project Folders#40

Merged
pablopunk merged 46 commits intomainfrom
sprint1-deployment-refactor
Feb 17, 2026
Merged

Encapsulate Deployments in Project Folders#40
pablopunk merged 46 commits intomainfrom
sprint1-deployment-refactor

Conversation

@pablopunk
Copy link
Owner

@pablopunk pablopunk commented Jan 28, 2026

Overview

This Sprint 1 refactor encapsulates all project files into a single folder structure, making deployment management simpler and more intuitive.

Key Changes

New Project Structure

data/projects/{projectId}/
├── .env                     # Port configuration
├── preview/                 # Development Environment
│   ├── src/                # Source code (AI works here)
│   ├── dist/               # Dev build output
│   ├── opencode/           # AI agent session
│   ├── docker-compose.yml  # Dev containers
│   └── logs/
└── production/              # Production Deployments
    ├── current/           → Symlink to active hash
    └── {hash}/             # Each version self-contained
        ├── dist/
        ├── node_modules/
        ├── docker-compose.yml
        └── logs/

Simplified Port System

Container Range Purpose
Dev preview Random (4000-9999) Dev server
OpenCode Random (4000-9999) AI agent
Production 8000-9999 (deterministic) Deployed sites

Previously: 4 ports per project (dev, opencode, base port, version port)
Now: 3 ports per project (dev, opencode, productionPort)

URL pattern: localhost:{productionPort} - always consistent for a project!

Removed nginx Dependency

  • Before: nginx routes basePort (3000-3999) → versionPort (5000-5999)
  • After: Direct port exposure, no nginx config needed
  • Benefit: Simpler setup, no root permissions required

Benefits

Complete encapsulation - Delete project = delete everything (dev + production)
Simpler ports - Predictable production URLs (8000-9999 range)
No nginx - No config files, no root access needed
Easy rollback - Keep last N versions in production/{hash}/
Atomic deployments - Switch symlink changes active version instantly

Implementation Details

Updated Files

  • Schema: Made productionPort NOT NULL (default: 8001)
  • Paths: Added getProjectPreviewPath(), getProjectProductionPath()
  • Port Allocation: Added allocateProjectProductionPort() for deterministic port assignment
  • Setup: Creates preview/ and production/ subdirectories during project creation
  • ProductionBuild: Runs pnpm build in preview/src/, copies to production/{hash}/dist/
  • ProductionStart: Stops old container, builds image, starts new container (no nginx)
  • ProductionStop: Stops and removes container directly
  • Rollback: Rebuilds container for target version and restarts

Breaking Changes

  • Existing projects need DB migration to set productionPort (default: 8001)
  • Existing production deployments will need redeployment to use new structure
  • nginx configuration (if used) is no longer needed

Testing

Create a project, deploy, and verify:

  • Project appears in data/projects/{projectId}/preview/ and production/
  • Production URL is localhost:xxxx (8000-9999 range)
  • Version history shows in production/{hash}/ folders
  • Rollback switches to previous version by rebuilding container

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Docker-based production deployment with automatic port allocation for improved scalability.
    • Added support for Kimi (k2.5) and Gemini 3.0 Flash AI models.
    • Enhanced log streaming initialization for better debugging and monitoring.
  • Chores

    • Updated database schema to enforce production configuration requirements.
    • Refined build and deployment infrastructure for stability.

This Sprint 1 refactor moves all project files into a single folder for better
ownership and simplify the port system from 4 ports per project to 3.

Key Changes:
- Encapsulated structure: Each project now has preview/ and production/ folders
  under the main project directory
- Simplified ports: Removed nginx routing, now expose productionPort directly
  on localhost:8000-9999 range (deterministic based on project ID)
- Production builds now use preview/src/ dist/ and deploy to production/{hash}/
- All deployment handlers updated to work without nginx (using docker run directly)
- Rollback now rebuilds container rather than changing nginx routing

Benefits:
- Delete project = delete everything (no separate production/ folder needed)
- No nginx requirement (simpler deployment)
- Consistent production URLs (localhost:{productionPort} always the same for a project)
- All versions tracked in production/{hash}/ folders with easy rollback

Affected files:
- Schema: Made productionPort NOT NULL with default 8001
- Paths: Added getProjectPreviewPath, getProjectProductionPath
- Port allocation: Added allocateProjectProductionPort (8000-9999 range)
- Setup: Creates preview/ and production/ subdirectories
- Handlers: Updated productionBuild, productionStart, productionStop
- Actions: Updated getProductionStatus, getProductionHistory, rollback
@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors production deployment from nginx-based routing to Docker containers, introduces per-project production port allocation, reorganizes project file structure into separate preview and production directories, and enhances bootstrap/startup workflows with new scripts and log streaming capabilities.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
src/server/db/schema.ts, drizzle/0000_brief_tempest.sql, drizzle/meta/_journal.json, drizzle/meta/0000_snapshot.json
Enforce NOT NULL constraints on productionPort and productionStatus columns; update migration journal and snapshot metadata.
Port Allocation
src/server/ports/allocate.ts
Add allocateProjectProductionPort() function that deterministically derives production ports (8000-9999) from project ID with fallback allocation and availability checking. Note: function appears duplicated in diff.
Production Deployment Handlers
src/server/queue/handlers/productionStart.ts, src/server/queue/handlers/productionStop.ts, src/server/queue/handlers/productionBuild.ts
Replace nginx-based routing with Docker container lifecycle: build images, run containers with port mapping, manage symlinks, remove old containers/images on stop. Introduce stopProductionContainer() helper and updated logging.
Project Structure & Paths
src/server/projects/paths.ts, src/server/projects/setup.ts
Introduce getProjectPreviewPath() and getProjectProductionPath() helpers; reorganize production paths under project/[projectId]/production/; update setupProjectFilesystem() to allocate and return productionPort; add separate preview/production directory creation.
Docker Compose & Utilities
src/server/docker/compose.ts, src/server/docker/logs.ts, src/server/queue/handlers/dockerComposeUp.ts, src/server/queue/handlers/dockerEnsureRunning.ts, src/server/queue/handlers/dockerStop.ts
Add --env-file flag to Docker Compose commands; introduce isStreamingActive(), ensureLogStreaming(), and checkContainersRunning() for log streaming management; update handlers to use preview paths.
Project Lifecycle
src/server/queue/handlers/projectCreate.ts, src/server/queue/handlers/projectDelete.ts, src/server/productions/cleanup.ts
Pass productionPort from setupProjectFilesystem() to project creation; enhance delete handler to stop/remove production containers and images; update fs import to use native fs/promises.
Bootstrap & Startup Scripts
scripts/bootstrap.sh, scripts/start-preview.sh, package.json, Dockerfile, docker-compose.preview.yml
Replace inline bootstrap command with external script; add startup script with database recovery fallback for preview environments; copy scripts into Docker image; set PREVIEW_ENV variable.
API & Log Streaming
src/pages/api/projects/[id]/logs.ts
Enable log streaming initialization on API requests via ensureLogStreaming() with non-blocking fire-and-forget pattern.
UI & Model Configuration
src/components/dashboard/ModelSelector.tsx, src/components/ui/svgs/kimi.tsx, src/server/config/models.ts, opencode.json, .github/workflows/opencode.yml
Add Kimi SVG component and vendor logos mapping; extend CURATED_MODELS list; update opencode.json with Kimi model and MCP integrations; add OPENCODE_PERMISSION environment variable in workflow.
CI/CD Workflows
.github/workflows/pr-preview.yml
Minor tar argument reordering in artifact archival step.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ProjectCreate as Project<br/>Create
    participant PortAlloc as Port<br/>Allocator
    participant FileSetup as File<br/>System Setup
    participant Database as Database
    participant ProjectQueue as Queue<br/>(Production Build)
    
    Client->>ProjectCreate: Create project
    ProjectCreate->>PortAlloc: Allocate port<br/>(8000-9999)
    PortAlloc->>PortAlloc: Compute hash,<br/>check availability
    PortAlloc-->>ProjectCreate: productionPort
    
    ProjectCreate->>FileSetup: Setup preview & production<br/>directories
    FileSetup->>FileSetup: Create preview/<br/>production paths
    FileSetup->>FileSetup: Write .env with<br/>productionPort
    FileSetup-->>ProjectCreate: { projectPath,<br/>productionPort }
    
    ProjectCreate->>Database: Create project record<br/>with productionPort
    Database-->>ProjectCreate: Success
    
    ProjectCreate->>ProjectQueue: Enqueue<br/>production:build
    ProjectQueue-->>Client: Project created
    
    Note over ProjectQueue: Later: Build & Start<br/>Production Container
    ProjectQueue->>ProjectQueue: Build Docker image<br/>doce-prod-<id>-<hash>
    ProjectQueue->>ProjectQueue: Run container:<br/>productionPort→3000
    ProjectQueue->>ProjectQueue: Update symlink<br/>to current hash
    ProjectQueue-->>Client: Production running
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Poem

🐰 Ports dance where containers play,
Docker routes the preview way,
Symlinks point to futures bright,
Bootstrap brings the startup right,
Kimi greets with logos new,
Production paths in split-view!


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

- Move --exclude flags before the source directory (.)
- tar requires options to precede positional arguments
- This fixes 'Exiting with failure status due to previous errors' in PR deployments
@github-actions
Copy link

Preview deployment failed.

Check the workflow logs for details.

@opencode-agent
Copy link

ProviderModelNotFoundError

github run

@github-actions
Copy link

Preview deployment failed.

Check the workflow logs for details.

@github-actions
Copy link

Preview deployment failed.

Check the workflow logs for details.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Review Comments

Bug: Wrong path in getProductionCurrentSymlink()

File: src/server/projects/paths.ts:102

The function getProductionCurrentSymlink() uses getProductionsPath() which is deprecated and returns the wrong path structure.

Issue

  • Returns: data/productions/{projectId}/current
  • Should return: data/projects/{projectId}/production/current

This breaks the new encapsulated project structure described in the PR.

Affected Code

The function at line 102:

return path.join(getProductionsPath(), projectId, "current");

Suggested Fix

export function getProductionCurrentSymlink(projectId: string): string {
	return path.join(getProjectProductionPath(projectId), "current");
}

This bug affects symlink operations in:

  • cleanup.ts:71, 144
  • productionStart.ts:134
  • actions/projects.ts:472

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style Issue: Unnecessary type assertions

File: src/server/queue/handlers/productionStart.ts:126-130, 212-215

The code uses awkward as unknown as type assertions to satisfy the typing for logger calls.

Line 126-130

logger.info(
  {
    projectId: project.id,
    containerName,
    productionPort,
  } as unknown as typeof runResult & {
    containerName: string;
    productionPort: number;
    projectId: string;
  },
  "Production container started",
);

The assertion as unknown as typeof runResult & {...} is confusing and unnecessary.

Line 212-215

logger.debug(
  { projectId, containerName } as unknown as {
    projectId: string;
    containerName: string;
  },
  "Stopped and removed production container",
);

Same issue - the type assertion as unknown as { ... } is unnecessary.

Suggestion

Remove the type assertions and create simple, readable objects:

logger.info({
  projectId: project.id,
  containerName,
  productionPort,
}, "Production container started");

and

logger.debug({
  projectId,
  containerName,
}, "Stopped and removed production container");

While the current code works by satisfying the logger type constraints with the unnecessary assertions, it reduces code readability and follows an anti-pattern. The simpler version without assertions is cleaner and more maintainable.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: Missing Dockerfile.prod in production build

Files:

  • src/server/queue/handlers/productionBuild.ts:79-90
  • src/server/queue/handlers/productionStart.ts:65-69

The Issue

The production build handler copies files from preview/ to production/{hash}/ but fails to copy Dockerfile.prod.

In productionBuild.ts (lines 79-90), only these files are copied:

  • package.json
  • pnpm-lock.yaml
  • dist/

But then in productionStart.ts (line 65-69), it tries to build a Docker image:

const buildResult = await spawnCommand(
  "docker",
  ["build", "-t", imageName, "-f", "Dockerfile.prod", "."],
  { cwd: productionPath },
);

This will FAIL because Dockerfile.prod does not exist in production/{hash}/.

Impact

All production deployments will fail with: "docker build failed: unable to prepare context: stat .../Dockerfile.prod: no such file or directory"

Suggested Fix

Add in productionBuild.ts after line 90:

// Copy Dockerfile.prod for production builds
await fs.copyFile(
  path.join(previewPath, "Dockerfile.prod"),
  path.join(productionPath, "Dockerfile.prod"),
);

// Copy docker-entrypoint.prod.sh if it exists
try {
  await fs.copyFile(
    path.join(previewPath, "docker-entrypoint.prod.sh"),
    path.join(productionPath, "docker-entrypoint.prod.sh"),
  );
} catch (error) {
  // Entrypoint script missing, but container might work without it
}

Also consider copying any production-only configuration files that might be needed.

Note

Since production deployment now uses direct docker build/run instead of docker-compose, the docker-compose.yml file created by createProductionComposeFile() is now unused. Either:

  1. Remove createProductionComposeFile() call, or
  2. Keep it as documentation/artifact only

But definitely copy Dockerfile.prod!

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Cleanup: Remove deprecated functions

File: src/server/ports/allocate.ts:84-124

The allocateProjectBasePort() function is no longer used anywhere in the codebase.

Background

According to the PR description, the old port system was:

  • Before: 4 ports per project (dev, opencode, base port, version port)
  • After: 3 ports per project (dev, opencode, productionPort)

The function allocateProjectBasePort() was part of the old 4-port system and is now superseded by allocateProjectProductionPort().

Verification

grep -r "allocateProjectBasePort" --include="*.ts" src/

Result: Only the function declaration itself, no callers.

Suggestion

Remove the unused function:

  • allocateProjectBasePort() (lines 84-124)
  • Related registerBasePort() (lines 174-177) if also unused

This keeps the codebase clean and removes confusing, outdated APIs.

Note

Similarly, deriveVersionPort() and registerVersionPort() functions might be defunct if the old version port system was completely removed. Verify if these are still used before removing.

@github-actions
Copy link

Preview deployment failed.

Check the workflow logs for details.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

Pablo added 2 commits January 29, 2026 14:45
This commit fixes several critical bugs identified in the deployment refactor:

Critical Fixes:
- Fix getProductionCurrentSymlink() to use correct path (data/projects/{id}/production/current)
- Fix symlink target to use full path instead of just hash name
- Add missing file copies in productionBuild (Dockerfile.prod, config files, src/)
- Fix production container cleanup in projectDelete handler
- Fix rollback action to use correct symlink paths

Improvements:
- Remove unused docker-compose.yml creation from productionBuild (using docker run)
- Add Docker image cleanup in productionStop and projectDelete
- Fix race condition in temp symlink names (add Math.random())

These fixes ensure deployments work correctly with the new encapsulated structure.
@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

hash: payload.productionHash.slice(0, 8),
containerName,
productionPort,
} as unknown as typeof runResult & {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"## Style Issue: Unnecessary type assertions in logger calls

File: src/server/queue/handlers/productionStart.ts:126-130

The code uses awkward `as unknown as` type assertions that reduce readability and maintainability.

Issue

The assertion `as unknown as typeof runResult & {...}` is confusing and unnecessary for simple logger calls.

Suggested Fix

Remove the type assertion and create a simple, readable object:
```typescript
logger.info({
projectId: project.id,
containerName,
productionPort,
}, "Production container started");
```"

await spawnCommand("docker", ["rm", containerName]);

logger.debug(
{ projectId, containerName } as unknown as {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"## Style Issue: Unnecessary type assertions in stopProductionContainer

File: src/server/queue/handlers/productionStart.ts:214-220

The `stopProductionContainer` function also uses awkward `as unknown as` type assertions.

Issue at Line 215

```typescript
logger.debug(
{ projectId, containerName } as unknown as {
projectId: string;
containerName: string;
},
"Stopped and removed production container",
);
```

Suggested Fix

```typescript
logger.debug({
projectId,
containerName,
}, "Stopped and removed production container");
```"

@github-actions
Copy link

LGTM

The PR implements the Sprint 1 deployment refactor well. The code follows the AGENTS.md guidelines.

Issues found and addressed:

  • Style Issue (lines 126, 215 in productionStart.ts): Unnecessary as unknown as type assertions in logger calls reduce readability
  • Previous issue reports about Dockerfile.prod and paths.ts have already been fixed in the current code

Code quality:

  • Clean TypeScript with proper typing
  • Functions properly separated (e.g., stopProductionContainer helper)
  • No use of any types
  • Uses pnpm as required
  • Follows MVC pattern with proper separation of concerns

Suggested cleanup (optional): The unused allocateProjectBasePort() function in ports/allocate.ts could be removed as the port system has moved from 4-port to 3-port architecture.

@opencode-agent
Copy link

Done. I've reviewed PR #40 against the AGENTS.md style guide and posted comments for the violations found. Here's the summary:

LGTM - The code follows the guidelines well, with one style issue identified.

Style Issue Found:

  • Unnecessary as unknown as type assertions in logger calls at src/server/queue/handlers/productionStart.ts:126 and :215, which reduce readability

Previous Issues Already Fixed:

  • Dockerfile.prod is being copied correctly (productionBuild.ts:118-121)
  • getProductionCurrentSymlink() uses the correct path function (paths.ts:101)

The only other note is an optional cleanup suggestion - the unused allocateProjectBasePort() function in ports/allocate.ts could be removed since the port system has been refactored from 4-port to 3-port architecture.

All code changes adhere to clean code principles, use pnpm, avoid any types, and properly separate concerns.

github run

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@socket-security
Copy link

socket-security bot commented Feb 16, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedtsx@​4.21.01001008185100

View full report

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

Preview deployment failed.

Check the workflow logs for details.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@github-actions
Copy link

🚀 Preview deployed!

Open preview environment

Note: Accessible via Tailscale network

ℹ️ This preview will be automatically cleaned up when the PR is closed.

@pablopunk pablopunk merged commit ba8c302 into main Feb 17, 2026
5 checks passed
@pablopunk pablopunk deleted the sprint1-deployment-refactor branch February 17, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant