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.
This commit is contained in:
55
README.md
55
README.md
@@ -389,3 +389,58 @@ For MinIO in production, consider:
|
|||||||
- Setting up proper networking and access controls
|
- Setting up proper networking and access controls
|
||||||
- Using multiple MinIO instances for high availability
|
- 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.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
backend:
|
backend:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
- VITE_API_URL=${VITE_API_URL:-http://backend:3000}
|
||||||
networks:
|
networks:
|
||||||
- worklenz
|
- worklenz
|
||||||
|
|
||||||
|
|||||||
34
update-docker-env.sh
Executable file
34
update-docker-env.sh
Executable file
@@ -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"
|
||||||
@@ -7,6 +7,10 @@ COPY package.json package-lock.json ./
|
|||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
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
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:22-alpine AS production
|
FROM node:22-alpine AS production
|
||||||
@@ -16,13 +20,13 @@ WORKDIR /app
|
|||||||
RUN npm install -g serve
|
RUN npm install -g serve
|
||||||
|
|
||||||
COPY --from=build /app/build /app/build
|
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\
|
RUN echo '#!/bin/sh\n\
|
||||||
cat > /app/build/env.js << EOL\n\
|
# Update env-config.js with runtime environment variables\n\
|
||||||
window.env = {\n\
|
cat > /app/build/env-config.js << EOL\n\
|
||||||
VITE_API_URL: "${VITE_API_URL}"\n\
|
window.VITE_API_URL="${VITE_API_URL:-http://backend:3000}";\n\
|
||||||
};\n\
|
|
||||||
EOL\n\
|
EOL\n\
|
||||||
exec serve -s build -l 5000' > /app/start.sh && \
|
exec serve -s build -l 5000' > /app/start.sh && \
|
||||||
chmod +x /app/start.sh
|
chmod +x /app/start.sh
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>Worklenz</title>
|
<title>Worklenz</title>
|
||||||
|
<!-- Environment configuration -->
|
||||||
|
<script src="/env-config.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import alertService from '@/services/alerts/alertService';
|
import alertService from '@/services/alerts/alertService';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
|
import config from '@/config/env';
|
||||||
|
|
||||||
export const getCsrfToken = (): string | null => {
|
export const getCsrfToken = (): string | null => {
|
||||||
const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN='));
|
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<string | null> => {
|
export const refreshCsrfToken = async (): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
// Make a GET request to the server to get a fresh CSRF token
|
// 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();
|
return getCsrfToken();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh CSRF token:', error);
|
console.error('Failed to refresh CSRF token:', error);
|
||||||
@@ -25,7 +26,7 @@ export const refreshCsrfToken = async (): Promise<string | null> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
baseURL: config.apiUrl,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types'
|
|||||||
import { IMyTask } from '@/types/home/my-tasks.types';
|
import { IMyTask } from '@/types/home/my-tasks.types';
|
||||||
import { IProject } from '@/types/project/project.types';
|
import { IProject } from '@/types/project/project.types';
|
||||||
import { getCsrfToken } from '../api-client';
|
import { getCsrfToken } from '../api-client';
|
||||||
|
import config from '@/config/env';
|
||||||
|
|
||||||
const rootUrl = '/home';
|
const rootUrl = '/home';
|
||||||
|
|
||||||
const api = createApi({
|
const api = createApi({
|
||||||
reducerPath: 'homePageApi',
|
reducerPath: 'homePageApi',
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`,
|
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
|
||||||
prepareHeaders: headers => {
|
prepareHeaders: headers => {
|
||||||
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
||||||
headers.set('Content-Type', 'application/json');
|
headers.set('Content-Type', 'application/json');
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import { IProjectsViewModel } from '@/types/project/projectsViewModel.types';
|
|||||||
import { IServerResponse } from '@/types/common.types';
|
import { IServerResponse } from '@/types/common.types';
|
||||||
import { IProjectMembersViewModel } from '@/types/projectMember.types';
|
import { IProjectMembersViewModel } from '@/types/projectMember.types';
|
||||||
import { getCsrfToken } from '../api-client';
|
import { getCsrfToken } from '../api-client';
|
||||||
|
import config from '@/config/env';
|
||||||
|
|
||||||
const rootUrl = '/projects';
|
const rootUrl = '/projects';
|
||||||
|
|
||||||
export const projectsApi = createApi({
|
export const projectsApi = createApi({
|
||||||
reducerPath: 'projectsApi',
|
reducerPath: 'projectsApi',
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
baseUrl: `${import.meta.env.VITE_API_URL}${API_BASE_URL}`,
|
baseUrl: `${config.apiUrl}${API_BASE_URL}`,
|
||||||
prepareHeaders: headers => {
|
prepareHeaders: headers => {
|
||||||
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
headers.set('X-CSRF-Token', getCsrfToken() || '');
|
||||||
headers.set('Content-Type', 'application/json');
|
headers.set('Content-Type', 'application/json');
|
||||||
|
|||||||
31
worklenz-frontend/src/config/env.ts
Normal file
31
worklenz-frontend/src/config/env.ts
Normal file
@@ -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(),
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user