From ed49eb0fce2294682ef0f7e91531a3d8517a5fa8 Mon Sep 17 00:00:00 2001 From: paul-nothaft <40865108-paul-nothaft@users.noreply.replit.com> Date: Thu, 23 Oct 2025 14:50:55 +0000 Subject: [PATCH] Improve deployment documentation and logging Update DOCKER-COMPOSE.md to include Drone CI deployment instructions and refactor server logging to dynamically import modules for production and development environments, separating Vite and static file serving logic into distinct files. 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 --- .replit | 2 +- DOCKER-COMPOSE.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++ server/index.ts | 30 +++++++++++++++++-- server/static.ts | 31 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 server/static.ts diff --git a/.replit b/.replit index b2b926c..aef2271 100644 --- a/.replit +++ b/.replit @@ -31,7 +31,7 @@ localPort = 44261 externalPort = 3003 [[ports]] -localPort = 45437 +localPort = 45711 externalPort = 4200 [env] diff --git a/DOCKER-COMPOSE.md b/DOCKER-COMPOSE.md index 4f101f1..da620b8 100644 --- a/DOCKER-COMPOSE.md +++ b/DOCKER-COMPOSE.md @@ -393,6 +393,80 @@ To use an external PostgreSQL database instead of the container: DATABASE_URL=postgresql://user:password@external-host:5432/taskflow ``` +## Deployment via CI/CD (Drone CI) + +If you're using Drone CI to build and deploy your Docker images: + +### Using Pre-built Images + +If your CI system (like Drone) builds the image for you: + +1. **Pull the image from your registry:** + ```bash + docker pull registry.local.nothaft.cloud/taskflow:latest + ``` + +2. **Create docker-compose.yml for pre-built image:** + ```yaml + version: '3.8' + + services: + db: + image: postgres:16-alpine + environment: + - POSTGRES_USER=${POSTGRES_USER:-taskflow} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB:-taskflow} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-taskflow}"] + interval: 5s + timeout: 5s + retries: 5 + + app: + image: registry.local.nothaft.cloud/taskflow:latest # Use pre-built image + ports: + - "${PORT:-5000}:5000" + environment: + - NODE_ENV=production + - DATABASE_URL=postgresql://${POSTGRES_USER:-taskflow}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-taskflow} + - PORT=5000 + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + volumes: + postgres_data: + ``` + +3. **Start services:** + ```bash + docker-compose up -d + ``` + +### .drone.yml Configuration + +Your `.drone.yml` file should build and push to your registry: + +```yaml +kind: pipeline +type: docker +name: default + +steps: + - name: build taskflow + image: plugins/docker + settings: + repo: registry.local.nothaft.cloud/taskflow + tags: latest + dockerfile: Dockerfile + context: . + registry: registry.local.nothaft.cloud +``` + ## Support For issues and questions: diff --git a/server/index.ts b/server/index.ts index db53764..3edc89b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,6 +1,5 @@ import express, { type Request, Response, NextFunction } from "express"; import { registerRoutes } from "./routes"; -import { setupVite, serveStatic, log } from "./vite"; import { initializeDatabase, closeDatabase } from "./db"; const app = express(); @@ -21,6 +20,13 @@ app.use((req, res, next) => { res.on("finish", () => { const duration = Date.now() - start; if (path.startsWith("/api")) { + const formattedTime = new Date().toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + second: "2-digit", + hour12: true, + }); + let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`; if (capturedJsonResponse) { logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`; @@ -30,7 +36,7 @@ app.use((req, res, next) => { logLine = logLine.slice(0, 79) + "…"; } - log(logLine); + console.log(`${formattedTime} [express] ${logLine}`); } }); @@ -38,6 +44,22 @@ app.use((req, res, next) => { }); (async () => { + // Dynamically import utilities based on environment + let log: (message: string, source?: string) => void; + let serveStatic: (app: express.Express) => void; + let setupVite: ((app: express.Express, server: any) => Promise) | undefined; + + if (app.get("env") === "development") { + const viteModule = await import("./vite"); + log = viteModule.log; + setupVite = viteModule.setupVite; + serveStatic = viteModule.serveStatic; + } else { + const staticModule = await import("./static"); + log = staticModule.log; + serveStatic = staticModule.serveStatic; + } + // Initialize database if using production mode or USE_DB is true if (process.env.NODE_ENV === 'production' || process.env.USE_DB === 'true') { try { @@ -62,7 +84,9 @@ app.use((req, res, next) => { // setting up all the other routes so the catch-all route // doesn't interfere with the other routes if (app.get("env") === "development") { - await setupVite(app, server); + if (setupVite) { + await setupVite(app, server); + } } else { serveStatic(app); } diff --git a/server/static.ts b/server/static.ts new file mode 100644 index 0000000..6ed009f --- /dev/null +++ b/server/static.ts @@ -0,0 +1,31 @@ +import express, { type Express } from "express"; +import fs from "fs"; +import path from "path"; + +export function log(message: string, source = "express") { + const formattedTime = new Date().toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + second: "2-digit", + hour12: true, + }); + + console.log(`${formattedTime} [${source}] ${message}`); +} + +export function serveStatic(app: Express) { + const distPath = path.resolve(import.meta.dirname, "public"); + + if (!fs.existsSync(distPath)) { + throw new Error( + `Could not find the build directory: ${distPath}, make sure to build the client first`, + ); + } + + app.use(express.static(distPath)); + + // fall through to index.html if the file doesn't exist + app.use("*", (_req, res) => { + res.sendFile(path.resolve(distPath, "index.html")); + }); +}