From 2a3ae31e4e4a0bc7b0a29ff542169b7337377530 Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Mon, 28 Apr 2025 11:32:44 +0530 Subject: [PATCH] Enhance Docker deployment with environment variable configuration - Added environment variable setup in docker-compose.yml for VITE_API_URL. - Introduced update-docker-env.sh script to create/update .env file for local and remote deployments. - Updated Dockerfile to dynamically create env-config.js during build. - Modified frontend to load environment configuration from env-config.js. - Refactored API client to use centralized config for API URL. --- README.md | 55 +++++++++++++++++++ docker-compose.yml | 2 + update-docker-env.sh | 34 ++++++++++++ worklenz-frontend/Dockerfile | 14 +++-- worklenz-frontend/index.html | 2 + worklenz-frontend/src/api/api-client.ts | 5 +- .../api/home-page/home-page.api.service.ts | 3 +- .../api/projects/projects.v1.api.service.ts | 3 +- worklenz-frontend/src/config/env.ts | 31 +++++++++++ 9 files changed, 140 insertions(+), 9 deletions(-) create mode 100755 update-docker-env.sh create mode 100644 worklenz-frontend/src/config/env.ts diff --git a/README.md b/README.md index 92ed1a90..2fe34797 100644 --- a/README.md +++ b/README.md @@ -389,3 +389,58 @@ For MinIO in production, consider: - Setting up proper networking and access controls - Using multiple MinIO instances for high availability +## Docker Deployment + +### Local Development with Docker + +1. Set up the environment variables: + ```bash + ./update-docker-env.sh + ``` + + This will create a `.env` file with default settings for local development. + +2. Run the application using Docker Compose: + ```bash + docker-compose up -d + ``` + +3. Access the application: + - Frontend: http://localhost:5000 + - Backend API: http://localhost:3000 + +### Remote Server Deployment + +When deploying to a remote server: + +1. Set up the environment variables with your server's hostname: + ```bash + ./update-docker-env.sh your-server-hostname + ``` + + This ensures that the frontend correctly connects to the backend API. + +2. Pull and run the latest Docker images: + ```bash + docker-compose pull + docker-compose up -d + ``` + +3. Access the application through your server's hostname: + - Frontend: http://your-server-hostname:5000 + - Backend API: http://your-server-hostname:3000 + +### Environment Configuration + +The Docker setup uses environment variables to configure the services: + +- Frontend: + - `VITE_API_URL`: URL of the backend API (default: http://backend:3000 for container networking) + +- Backend: + - Database connection parameters + - Storage configuration + - Other backend settings + +For custom configuration, edit the `.env` file or the `update-docker-env.sh` script. + diff --git a/docker-compose.yml b/docker-compose.yml index c80eb976..30e9c058 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: depends_on: backend: condition: service_started + environment: + - VITE_API_URL=${VITE_API_URL:-http://backend:3000} networks: - worklenz diff --git a/update-docker-env.sh b/update-docker-env.sh new file mode 100755 index 00000000..5a7fc614 --- /dev/null +++ b/update-docker-env.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Script to set environment variables for Docker deployment +# Usage: ./update-docker-env.sh [hostname] + +# Default hostname if not provided +DEFAULT_HOSTNAME="localhost" +HOSTNAME=${1:-$DEFAULT_HOSTNAME} + +# Create or update root .env file +cat > .env << EOL +# Frontend Configuration +VITE_API_URL=http://${HOSTNAME}:3000 + +# Backend Configuration +DB_HOST=db +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=worklenz_db +NODE_ENV=development +PORT=3000 + +# Storage Configuration +AWS_REGION=us-east-1 +STORAGE_PROVIDER=s3 +BUCKET=worklenz-bucket +S3_ACCESS_KEY_ID=minioadmin +S3_SECRET_ACCESS_KEY=minioadmin +S3_URL=http://minio:9000 +EOL + +echo "Environment configuration updated for ${HOSTNAME}" +echo "To run with Docker Compose, use: docker-compose up -d" \ No newline at end of file diff --git a/worklenz-frontend/Dockerfile b/worklenz-frontend/Dockerfile index 5b1c338d..512ec923 100644 --- a/worklenz-frontend/Dockerfile +++ b/worklenz-frontend/Dockerfile @@ -7,6 +7,10 @@ COPY package.json package-lock.json ./ RUN npm ci COPY . . + +# Create env-config.js dynamically during build +RUN echo "window.VITE_API_URL='${VITE_API_URL:-http://backend:3000}';" > ./public/env-config.js + RUN npm run build FROM node:22-alpine AS production @@ -16,13 +20,13 @@ WORKDIR /app RUN npm install -g serve COPY --from=build /app/build /app/build +COPY --from=build /app/public/env-config.js /app/build/env-config.js -# Create a script to inject environment variables +# Create a script to start server and dynamically update env-config.js RUN echo '#!/bin/sh\n\ -cat > /app/build/env.js << EOL\n\ -window.env = {\n\ - VITE_API_URL: "${VITE_API_URL}"\n\ -};\n\ +# Update env-config.js with runtime environment variables\n\ +cat > /app/build/env-config.js << EOL\n\ +window.VITE_API_URL="${VITE_API_URL:-http://backend:3000}";\n\ EOL\n\ exec serve -s build -l 5000' > /app/start.sh && \ chmod +x /app/start.sh diff --git a/worklenz-frontend/index.html b/worklenz-frontend/index.html index bad8d4a3..86abad6e 100644 --- a/worklenz-frontend/index.html +++ b/worklenz-frontend/index.html @@ -12,6 +12,8 @@ rel="stylesheet" /> Worklenz + + diff --git a/worklenz-frontend/src/api/api-client.ts b/worklenz-frontend/src/api/api-client.ts index e87e0b61..ec43f7a5 100644 --- a/worklenz-frontend/src/api/api-client.ts +++ b/worklenz-frontend/src/api/api-client.ts @@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios'; import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; +import config from '@/config/env'; export const getCsrfToken = (): string | null => { const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN=')); @@ -16,7 +17,7 @@ export const getCsrfToken = (): string | null => { export const refreshCsrfToken = async (): Promise => { try { // Make a GET request to the server to get a fresh CSRF token - await axios.get(`${import.meta.env.VITE_API_URL}/csrf-token`, { withCredentials: true }); + await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true }); return getCsrfToken(); } catch (error) { console.error('Failed to refresh CSRF token:', error); @@ -25,7 +26,7 @@ export const refreshCsrfToken = async (): Promise => { }; const apiClient = axios.create({ - baseURL: import.meta.env.VITE_API_URL, + baseURL: config.apiUrl, withCredentials: true, headers: { 'Content-Type': 'application/json', diff --git a/worklenz-frontend/src/api/home-page/home-page.api.service.ts b/worklenz-frontend/src/api/home-page/home-page.api.service.ts index de83bd70..74f5615a 100644 --- a/worklenz-frontend/src/api/home-page/home-page.api.service.ts +++ b/worklenz-frontend/src/api/home-page/home-page.api.service.ts @@ -6,13 +6,14 @@ import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types' import { IMyTask } from '@/types/home/my-tasks.types'; import { IProject } from '@/types/project/project.types'; import { getCsrfToken } from '../api-client'; +import config from '@/config/env'; const rootUrl = '/home'; const api = createApi({ reducerPath: 'homePageApi', baseQuery: fetchBaseQuery({ - baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`, + baseUrl: `${config.apiUrl}${API_BASE_URL}`, prepareHeaders: headers => { headers.set('X-CSRF-Token', getCsrfToken() || ''); headers.set('Content-Type', 'application/json'); diff --git a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts index 8e6be422..1fe279d5 100644 --- a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts @@ -6,13 +6,14 @@ import { IProjectsViewModel } from '@/types/project/projectsViewModel.types'; import { IServerResponse } from '@/types/common.types'; import { IProjectMembersViewModel } from '@/types/projectMember.types'; import { getCsrfToken } from '../api-client'; +import config from '@/config/env'; const rootUrl = '/projects'; export const projectsApi = createApi({ reducerPath: 'projectsApi', baseQuery: fetchBaseQuery({ - baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`, + baseUrl: `${config.apiUrl}${API_BASE_URL}`, prepareHeaders: headers => { headers.set('X-CSRF-Token', getCsrfToken() || ''); headers.set('Content-Type', 'application/json'); diff --git a/worklenz-frontend/src/config/env.ts b/worklenz-frontend/src/config/env.ts new file mode 100644 index 00000000..10433940 --- /dev/null +++ b/worklenz-frontend/src/config/env.ts @@ -0,0 +1,31 @@ +/** + * Environment configuration + * Reads from window.VITE_API_URL (set by env-config.js) + * Falls back to import.meta.env.VITE_API_URL (set during build time) + * Falls back to a development default + */ + +declare global { + interface Window { + VITE_API_URL?: string; + } +} + +export const getApiUrl = (): string => { + // First check runtime-injected environment variables + if (window.VITE_API_URL) { + return window.VITE_API_URL; + } + + // Then check build-time environment variables + if (import.meta.env.VITE_API_URL) { + return import.meta.env.VITE_API_URL; + } + + // Default for development + return 'http://localhost:3000'; +}; + +export default { + apiUrl: getApiUrl(), +}; \ No newline at end of file