Files
task-manager/DOCKER-FIX.md
T
paul-nothaft d6d23e74e1
continuous-integration/drone/push Build is passing
Ensure shared schema is compiled for production deployment
Update Dockerfile and build process to compile shared/schema.ts using esbuild and copy the resulting schema.js to the production image.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/SBF5OKZ
2025-10-23 15:47:27 +00:00

7.3 KiB

Docker Deployment Fix - Vite Module Error

Problem

When deploying the TaskFlow application using a Docker image built by Drone CI, the container failed to start with the following error:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'vite' imported from /app/dist/index.js

Root Cause

The issue occurred because of two problems:

Problem 1: Bundled vite imports

  1. Build Process: The original build script used esbuild with the --bundle flag, which bundled all imports (including dynamic imports) into a single dist/index.js file
  2. Production Dependencies: The Dockerfile only installed production dependencies (excluding dev dependencies like vite)
  3. Import Resolution: Even though the code conditionally imported vite only in development, the bundled JavaScript still contained references to the vite module
  4. Runtime Failure: At runtime in production, Node.js tried to resolve the vite module which wasn't available in node_modules

Problem 2: Missing .js extensions in ESM imports

After fixing the bundling issue, a second error appeared:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/dist/routes' imported from /app/dist/index.js

This happened because:

  1. TypeScript Convention: TypeScript allows imports without .js extensions (e.g., import { x } from './module')
  2. esbuild Compilation: When esbuild compiles without bundling, it doesn't automatically add .js extensions to import paths
  3. Node.js ESM Requirement: Node.js ESM loader strictly requires explicit file extensions (e.g., import { x } from './module.js')
  4. Runtime Failure: Node.js couldn't resolve module paths without the .js extension

Problem 3: Shared schema not compiled

After fixing the import paths, a third error appeared:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/shared/schema.js' imported from /app/dist/routes.js

This happened because:

  1. TypeScript Source: The shared/schema.ts file was copied as-is (TypeScript)
  2. JavaScript Import: The compiled server files imported ../shared/schema.js
  3. Missing Compilation: The shared folder wasn't being compiled to JavaScript
  4. Runtime Failure: Node.js couldn't find schema.js because only schema.ts existed

Solution Implemented

1. Changed Build Strategy

Instead of bundling all server code into a single file, we now compile each server file separately without bundling:

Before (bundled):

esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist

After (separate compilation):

esbuild server/index.ts server/routes.ts server/storage.ts server/db.ts server/static.ts \
    --platform=node --packages=external --format=esm --outdir=dist

2. Added .js Extensions to All Imports

Updated all TypeScript files to include .js extensions in relative imports for Node.js ESM compatibility:

Before:

import { registerRoutes } from "./routes";
import { storage } from "./storage";
import * as schema from "@shared/schema";

After:

import { registerRoutes } from "./routes.js";
import { storage } from "./storage.js";
import * as schema from "../shared/schema.js";

Files updated:

  • server/index.ts - Added .js to ./routes.js, ./db.js, ./vite.js, ./static.js
  • server/routes.ts - Added .js to ./storage.js and changed @shared/schema to ../shared/schema.js
  • server/storage.ts - Changed @shared/schema to ../shared/schema.js
  • server/db.ts - Changed @shared/schema to ../shared/schema.js

3. Compiled Shared Schema

Added compilation step for the shared schema file:

# Build shared schema file
RUN npx esbuild shared/schema.ts --platform=node --packages=external --format=esm --outdir=shared

Then copy only the compiled JavaScript file to production:

COPY --from=build /app/shared/schema.js ./shared/

4. Updated Dockerfile

The Dockerfile now explicitly compiles both server and shared modules:

# Build server files separately (without bundling) - only production files
RUN npx esbuild server/index.ts server/routes.ts server/storage.ts server/db.ts server/static.ts \
    --platform=node --packages=external --format=esm --outdir=dist

# Build shared schema file
RUN npx esbuild shared/schema.ts --platform=node --packages=external --format=esm --outdir=shared

And copies only the compiled JavaScript files to production:

# Copy built application
COPY --from=build /app/dist ./dist
COPY --from=build /app/shared/schema.js ./shared/

Created Production-Only Module

Created server/static.ts containing only production dependencies:

  • log() function for logging
  • serveStatic() function for serving built frontend assets
  • No vite imports

Conditional Module Loading

Updated server/index.ts to dynamically load the appropriate module:

if (app.get("env") === "development") {
  const viteModule = await import("./vite");  // Loads vite in dev
  // ...
} else {
  const staticModule = await import("./static");  // Loads production module
  // ...
}

How It Works Now

Development Mode

  1. NODE_ENV is set to "development"
  2. Server imports ./vite module (includes vite dependency)
  3. Vite dev server starts with HMR

Production Mode (Docker)

  1. NODE_ENV is set to "production"
  2. Server imports ./static module (no vite dependency)
  3. Serves pre-built static files from dist/public
  4. vite is never loaded or required

Files Modified

  • server/static.ts - New production-only module
  • server/index.ts - Conditional module loading + .js extensions
  • server/routes.ts - Added .js extensions to imports
  • server/storage.ts - Added .js extensions to imports
  • server/db.ts - Added .js extensions to imports
  • Dockerfile - Separate server file compilation

Testing

Local Development

npm run dev
# Should work as before with Vite HMR

Docker Build & Run

# Build image (or let Drone CI do it)
docker build -t taskflow:latest .

# Run with docker-compose
docker-compose up -d

# Check logs
docker-compose logs -f app

# Should see:
# serving on port 5000
# Database schema is up to date
# ✓ Database initialized successfully

Benefits

  1. Smaller Production Image - Only production dependencies included
  2. Faster Startup - No vite module loading in production
  3. Cleaner Separation - Development and production code paths are separate
  4. Easier Debugging - Separate compiled files instead of one bundled file
  5. No Runtime Errors - Production never tries to load vite

Drone CI Integration

Your .drone.yml file doesn't need any changes. The Docker build process now correctly handles the server compilation.

When Drone builds your image, it will:

  1. Install all dependencies (dev + prod) in build stage
  2. Build frontend with Vite
  3. Compile server files separately
  4. Copy only production dependencies to final image
  5. Copy compiled server files (without vite references)

Deployment

After Drone builds and pushes your image, deploy it with:

# Pull latest image
docker pull registry.local.nothaft.cloud/taskflow:latest

# Start with docker-compose
docker-compose up -d

The application will now start successfully without the vite module error! 🚀