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
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
- Build Process: The original build script used esbuild with the
--bundleflag, which bundled all imports (including dynamic imports) into a singledist/index.jsfile - Production Dependencies: The Dockerfile only installed production dependencies (excluding dev dependencies like
vite) - Import Resolution: Even though the code conditionally imported vite only in development, the bundled JavaScript still contained references to the vite module
- 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:
- TypeScript Convention: TypeScript allows imports without
.jsextensions (e.g.,import { x } from './module') - esbuild Compilation: When esbuild compiles without bundling, it doesn't automatically add
.jsextensions to import paths - Node.js ESM Requirement: Node.js ESM loader strictly requires explicit file extensions (e.g.,
import { x } from './module.js') - Runtime Failure: Node.js couldn't resolve module paths without the
.jsextension
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:
- TypeScript Source: The
shared/schema.tsfile was copied as-is (TypeScript) - JavaScript Import: The compiled server files imported
../shared/schema.js - Missing Compilation: The shared folder wasn't being compiled to JavaScript
- Runtime Failure: Node.js couldn't find
schema.jsbecause onlyschema.tsexisted
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.jsto./routes.js,./db.js,./vite.js,./static.jsserver/routes.ts- Added.jsto./storage.jsand changed@shared/schemato../shared/schema.jsserver/storage.ts- Changed@shared/schemato../shared/schema.jsserver/db.ts- Changed@shared/schemato../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 loggingserveStatic()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
- NODE_ENV is set to "development"
- Server imports
./vitemodule (includes vite dependency) - Vite dev server starts with HMR
Production Mode (Docker)
- NODE_ENV is set to "production"
- Server imports
./staticmodule (no vite dependency) - Serves pre-built static files from
dist/public - vite is never loaded or required
Files Modified
- ✅
server/static.ts- New production-only module - ✅
server/index.ts- Conditional module loading +.jsextensions - ✅
server/routes.ts- Added.jsextensions to imports - ✅
server/storage.ts- Added.jsextensions to imports - ✅
server/db.ts- Added.jsextensions 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
- ✅ Smaller Production Image - Only production dependencies included
- ✅ Faster Startup - No vite module loading in production
- ✅ Cleaner Separation - Development and production code paths are separate
- ✅ Easier Debugging - Separate compiled files instead of one bundled file
- ✅ 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:
- Install all dependencies (dev + prod) in build stage
- Build frontend with Vite
- Compile server files separately
- Copy only production dependencies to final image
- 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! 🚀