fix(ci): dockerfile build script

This commit is contained in:
Ahmed Bouhuolia
2026-01-28 23:40:32 +02:00
parent f5bfdede30
commit 7874b9f765
12 changed files with 249 additions and 118 deletions

93
.dockerignore Normal file
View File

@@ -0,0 +1,93 @@
# Dependencies
node_modules/
**/node_modules/
.pnpm-store/
# Build outputs
dist/
build/
**/dist/
**/build/
*.tsbuildinfo
# Development files
.git/
.gitignore
.vscode/
.idea/
*.swp
*.swo
*~
# Test files
test/
**/test/
**/*.spec.ts
**/*.test.ts
**/*.e2e-spec.ts
coverage/
.nyc_output/
test-results/
playwright-report/
# Documentation
*.md
!README.md
docs/
CHANGELOG.md
CONTRIBUTING.md
DISCLAIMER
LICENSE
# CI/CD
.github/
.gitpod.yml
# Environment files
.env
.env.*
!.env.example
# Logs
*.log
logs/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# OS files
.DS_Store
Thumbs.db
*.pid
*.seed
*.pid.lock
# Docker files (don't copy Dockerfiles into themselves)
docker-compose*.yml
Dockerfile*
.dockerignore
# Misc
.cache/
.temp/
tmp/
*.tmp
.qodo/
e2e/
playwright.config.ts
# Source maps (not needed in production)
*.map
# TypeScript configs (not needed at runtime)
tsconfig*.json
!tsconfig.json
# Linting/formatting
.eslintrc*
.prettierrc*
.eslintcache
# Package manager locks (we copy them explicitly)
# pnpm-lock.yaml

View File

@@ -58,6 +58,12 @@ services:
# System database
- SYSTEM_DB_NAME=${SYSTEM_DB_NAME}
# Redis
- REDIS_HOST=redis
- REDIS_PORT=6379
- QUEUE_HOST=redis
- QUEUE_PORT=6379
# Tenants databases
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}

View File

@@ -35,4 +35,4 @@ WORKDIR /app/packages/server
RUN git clone https://github.com/vishnubob/wait-for-it.git
# Once we listen the mysql port run the migration task.
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest"
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "pnpm run system:migrate:latest && pnpm run tenants:migrate:latest"

View File

@@ -1,100 +1,99 @@
FROM node:18.16.0-alpine as build
# Stage 1: Build
FROM node:18.16.0-alpine AS builder
USER root
ARG MAIL_HOST= \
MAIL_USERNAME= \
MAIL_PASSWORD= \
MAIL_PORT= \
MAIL_SECURE= \
MAIL_FROM_NAME= \
MAIL_FROM_ADDRESS= \
# Database
DB_HOST= \
DB_USER= \
DB_PASSWORD= \
DB_CHARSET= \
# System database.
SYSTEM_DB_NAME= \
SYSTEM_DB_PASSWORD= \
SYSTEM_DB_USER= \
SYSTEM_DB_HOST= \
SYSTEM_DB_CHARSET= \
# Tenant databases.
TENANT_DB_USER= \
TENANT_DB_PASSWORD= \
TENANT_DB_HOST= \
TENANT_DB_NAME_PERFIX= \
TENANT_DB_CHARSET= \
# Authentication
JWT_SECRET= \
# Application
BASE_URL= \
# Sign-up restriction
SIGNUP_DISABLED= \
SIGNUP_ALLOWED_DOMAINS= \
SIGNUP_ALLOWED_EMAILS=
ENV MAIL_HOST=$MAIL_HOST \
MAIL_USERNAME=$MAIL_USERNAME \
MAIL_PASSWORD=$MAIL_PASSWORD \
MAIL_PORT=$MAIL_PORT \
MAIL_SECURE=$MAIL_SECURE \
MAIL_FROM_NAME=$MAIL_FROM_NAME \
MAIL_FROM_ADDRESS=$MAIL_FROM_ADDRESS \
# Database
DB_HOST=$DB_HOST \
DB_USER=$DB_USER \
DB_PASSWORD=$DB_PASSWORD \
DB_CHARSET=$DB_CHARSET \
# System database.
SYSTEM_DB_HOST=$SYSTEM_DB_HOST \
SYSTEM_DB_USER=$SYSTEM_DB_USER \
SYSTEM_DB_PASSWORD=$SYSTEM_DB_PASSWORD \
SYSTEM_DB_NAME=$SYSTEM_DB_NAME \
SYSTEM_DB_CHARSET=$SYSTEM_DB_CHARSET \
# Tenant databases.
TENANT_DB_NAME_PERFIX=$TENANT_DB_NAME_PERFIX \
TENANT_DB_HOST=$TENANT_DB_HOST \
TENANT_DB_PASSWORD=$TENANT_DB_PASSWORD \
TENANT_DB_USER=$TENANT_DB_USER \
TENANT_DB_CHARSET=$TENANT_DB_CHARSET \
# Authentication
JWT_SECRET=$JWT_SECRET \
# Application
BASE_URL=$BASE_URL \
# Sign-up restriction
SIGNUP_DISABLED=$SIGNUP_DISABLED \
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
# New Relic config file.
ENV NEW_RELIC_NO_CONFIG_FILE=true
# Create app directory.
WORKDIR /app
RUN chown node:node /
# Install pnpm
RUN npm install -g pnpm@8.10.2
# Install pnpm
RUN npm install -g pnpm
# Install build dependencies
RUN apk add --no-cache python3 build-base chromium
# Copy application dependency manifests to the container image.
COPY --chown=node:node ./ ./
# Install application dependencies
RUN apk update
RUN apk add python3 build-base chromium
# Set PYHTON env
# Set Python environment
ENV PYTHON=/usr/bin/python3
# Install packages dependencies for production.
RUN pnpm install
# Copy package files for dependency installation
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./
COPY --chown=node:node packages/server/package.json ./packages/server/
COPY --chown=node:node shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
COPY --chown=node:node shared/pdf-templates/package.json ./shared/pdf-templates/
COPY --chown=node:node shared/email-components/package.json ./shared/email-components/
# Install all dependencies (including devDependencies for build)
RUN pnpm install --frozen-lockfile
# Copy source code
COPY --chown=node:node ./packages/server ./packages/server
COPY --chown=node:node ./shared/bigcapital-utils ./shared/bigcapital-utils
COPY --chown=node:node ./shared/pdf-templates ./shared/pdf-templates
COPY --chown=node:node ./shared/email-components ./shared/email-components
# # Creates a "dist" folder with the production build
# Build NestJS application
RUN pnpm run build:server --skip-nx-cache
CMD [ "node", "./packages/server/build/index.js" ]
# Stage 2: Production
FROM node:18.16.0-alpine AS production
WORKDIR /app
# Install pnpm for production
RUN npm install -g pnpm@8.10.2
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Install build dependencies for native modules (bcrypt, etc.)
RUN apk add --no-cache python3 build-base
# Set Python environment
ENV PYTHON=/usr/bin/python3
# Copy package files for production dependency installation
COPY --chown=nodejs:nodejs package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY --chown=nodejs:nodejs packages/server/package.json ./packages/server/
COPY --chown=nodejs:nodejs shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
COPY --chown=nodejs:nodejs shared/pdf-templates/package.json ./shared/pdf-templates/
COPY --chown=nodejs:nodejs shared/email-components/package.json ./shared/email-components/
# Copy .husky directory (needed for husky install command)
COPY --chown=nodejs:nodejs .husky ./.husky
# Install only production dependencies
# Install husky temporarily so prepare script can run, then remove it
RUN pnpm add -D -w husky && \
pnpm install --prod --frozen-lockfile && \
pnpm remove -w husky && \
# Remove build dependencies to reduce image size
apk del python3 build-base
# Copy built application from builder stage
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/dist ./packages/server/dist
# Copy static assets (i18n, public, static directories)
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/src/i18n ./packages/server/dist/i18n
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/public ./packages/server/public
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/static ./packages/server/static
# Copy built shared packages (dist folders and package.json for module resolution)
COPY --from=builder --chown=nodejs:nodejs /app/shared/bigcapital-utils/dist ./shared/bigcapital-utils/dist
COPY --from=builder --chown=nodejs:nodejs /app/shared/pdf-templates/dist ./shared/pdf-templates/dist
COPY --from=builder --chown=nodejs:nodejs /app/shared/email-components/dist ./shared/email-components/dist
# Set runtime environment variables (these should be provided at runtime via docker-compose or k8s)
ENV NODE_ENV=production
ENV NEW_RELIC_NO_CONFIG_FILE=true
ENV PORT=3000
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3000
# Health check - uses /api/system_db ping endpoint
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/system_db', (r) => {process.exit(r.statusCode >= 200 && r.statusCode < 300 ? 0 : 1)}).on('error', () => process.exit(1))"
# Start the application
CMD [ "node", "packages/server/dist/main.js" ]

View File

@@ -17,6 +17,8 @@ import loops from './loops';
import bankfeed from './bankfeed';
import throttle from './throttle';
import cloud from './cloud';
import redis from './redis';
import queue from './queue';
export const config = [
app,
@@ -38,4 +40,6 @@ export const config = [
loops,
bankfeed,
throttle,
redis,
queue,
];

View File

@@ -0,0 +1,6 @@
import { registerAs } from '@nestjs/config';
export default registerAs('queue', () => ({
host: process.env.QUEUE_HOST || 'localhost',
port: parseInt(process.env.QUEUE_PORT, 10) || 6379,
}));

View File

@@ -137,8 +137,8 @@ import { AppThrottleModule } from './AppThrottle.module';
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
connection: {
host: configService.get('QUEUE_HOST'),
port: configService.get('QUEUE_PORT'),
host: configService.get('queue.host'),
port: configService.get('queue.port'),
},
}),
inject: [ConfigService],
@@ -158,8 +158,8 @@ import { AppThrottleModule } from './AppThrottle.module';
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
config: {
host: configService.get('redis.host') || 'localhost',
port: configService.get('redis.port') || 6379,
host: configService.get('redis.host'),
port: configService.get('redis.port'),
},
}),
inject: [ConfigService],

View File

@@ -1,27 +1,47 @@
FROM node:18.16.0-alpine as build
USER root
# Stage 1: Build
FROM node:18.16.0-alpine AS builder
WORKDIR /app
# Copy application dependency manifests to the container image.
COPY . .
# Install pnpm
RUN npm install -g pnpm@8.10.2
# Install application dependencies
RUN apk update
RUN apk add python3 build-base chromium
# Install build dependencies
RUN apk add --no-cache python3 build-base chromium
# Set PYHTON env
# Set Python environment
ENV PYTHON=/usr/bin/python3
# Install pnpm packages dependencies
RUN npm install -g pnpm
# Copy package files for dependency installation
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./
COPY --chown=node:node packages/webapp/package.json ./packages/webapp/
COPY --chown=node:node shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
COPY --chown=node:node shared/pdf-templates/package.json ./shared/pdf-templates/
COPY --chown=node:node shared/email-components/package.json ./shared/email-components/
# Install all dependencies (including devDependencies for build)
RUN pnpm install
# Copy source code for webapp and dependencies
COPY --chown=node:node ./packages/webapp ./packages/webapp
COPY --chown=node:node ./shared/bigcapital-utils ./shared/bigcapital-utils
COPY --chown=node:node ./shared/pdf-templates ./shared/pdf-templates
COPY --chown=node:node ./shared/email-components ./shared/email-components
# Build webapp package
RUN pnpm run build:webapp
FROM nginx
# Stage 2: Nginx
FROM nginx:alpine
COPY ./packages/webapp/nginx/sites/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/packages/webapp/dist /usr/share/nginx/html
# Copy nginx configuration
COPY --chown=root:root ./packages/webapp/nginx/sites/default.conf /etc/nginx/conf.d/default.conf
# Copy built webapp assets from builder stage
COPY --from=builder --chown=nginx:nginx /app/packages/webapp/dist /usr/share/nginx/html
# Expose port
EXPOSE 80
# Nginx runs as nginx user by default, which is good for security
# No CMD needed as nginx base image already has it

View File

@@ -2,7 +2,7 @@
import React, { useState } from 'react';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useQueryClient } from 'react-query';
import { FormattedMessage as T, AppToaster } from '@/components';
import { withAlertStoreConnect } from '@/containers/Alert/withAlertStoreConnect';
@@ -22,6 +22,7 @@ function AccountBulkActivateAlert({
requestBulkActivateAccounts,
}) {
const [isLoading, setLoading] = useState(false);
const queryClient = useQueryClient();
const selectedRowsCount = 0;
// Handle alert cancel.
@@ -38,9 +39,9 @@ function AccountBulkActivateAlert({
message: intl.get('the_accounts_has_been_successfully_activated'),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('accounts-table');
queryClient.invalidateQueries('accounts-table');
})
.catch((errors) => { })
.catch((errors) => {})
.finally(() => {
setLoading(false);
closeAlert(name);

View File

@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { FormattedMessage as T } from '@/components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useQueryClient } from 'react-query';
import { AppToaster } from '@/components';
// import { withAccountsActions } from '@/containers/Accounts/withAccountsTableActions';
@@ -22,8 +22,8 @@ function AccountBulkInactivateAlert({
closeAlert,
}) {
const [isLoading, setLoading] = useState(false);
const queryClient = useQueryClient();
const selectedRowsCount = 0;
// Handle alert cancel.
@@ -39,9 +39,9 @@ function AccountBulkInactivateAlert({
message: intl.get('the_accounts_have_been_successfully_inactivated'),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('accounts-table');
queryClient.invalidateQueries('accounts-table');
})
.catch((errors) => { })
.catch((errors) => {})
.finally(() => {
setLoading(false);
closeAlert(name);

View File

@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
import intl from 'react-intl-universal';
import { AppToaster, FormattedMessage as T } from '@/components';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useQueryClient } from 'react-query';
import { useApproveEstimate } from '@/hooks/query';
@@ -25,6 +25,7 @@ function EstimateApproveAlert({
// #withAlertActions
closeAlert,
}) {
const queryClient = useQueryClient();
const { mutateAsync: deliverEstimateMutate, isLoading } =
useApproveEstimate();
@@ -40,7 +41,7 @@ function EstimateApproveAlert({
message: intl.get('the_estimate_has_been_approved_successfully'),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('estimates-table');
queryClient.invalidateQueries('estimates-table');
})
.catch((error) => {})
.finally(() => {

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import React from 'react';
import { DialogContent } from '@/components';
import { useQuery, queryCache } from 'react-query';
import { useQuery, useQueryClient } from 'react-query';
import ReferenceNumberForm from '@/containers/JournalNumber/ReferenceNumberForm';
@@ -31,6 +31,7 @@ function BillNumberDialogContent({
// #withBillsActions
setBillNumberChanged,
}) {
const queryClient = useQueryClient();
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const handleSubmitForm = (values, { setSubmitting }) => {
@@ -45,7 +46,7 @@ function BillNumberDialogContent({
setBillNumberChanged(true);
setTimeout(() => {
queryCache.invalidateQueries('settings');
queryClient.invalidateQueries('settings');
}, 250);
})
.catch(() => {