make deployment easier (#225)
This commit is contained in:
74
Dockerfile
Normal file
74
Dockerfile
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM base AS deps
|
||||||
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install root dependencies
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Install frontend dependencies
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package.json frontend/package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Install backend dependencies
|
||||||
|
WORKDIR /app/backend
|
||||||
|
COPY backend/package.json backend/package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
FROM base AS frontend-builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json ./
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=deps /app/frontend/node_modules ./frontend/node_modules
|
||||||
|
COPY frontend ./frontend
|
||||||
|
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Final production image
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=8000
|
||||||
|
ENV FRONTEND_ORIGIN=http://localhost:8000
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy backend files
|
||||||
|
COPY backend ./backend
|
||||||
|
|
||||||
|
# Copy frontend build
|
||||||
|
COPY --from=frontend-builder /app/frontend/.next/standalone ./
|
||||||
|
COPY --from=frontend-builder /app/frontend/.next/static ./.next/static
|
||||||
|
COPY --from=frontend-builder /app/frontend/public ./public
|
||||||
|
|
||||||
|
# Copy Next.js module for the server
|
||||||
|
COPY --from=deps /app/frontend/node_modules/next ./node_modules/next
|
||||||
|
|
||||||
|
# Install backend dependencies directly in the container to ensure native modules are built correctly
|
||||||
|
WORKDIR /app/backend
|
||||||
|
COPY backend/package.json backend/package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy custom combined_server.js that combines frontend and backend
|
||||||
|
COPY combined_server.js ./
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
# Expose the port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run database migrations and start the combined server
|
||||||
|
CMD ["node", "combined_server.js"]
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
FROM node:20-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
RUN npm ci
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE $PORT
|
|
||||||
RUN npx sequelize db:migrate
|
|
||||||
|
|
||||||
CMD ["node", "index.js"]
|
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"storage": "database/database.sqlite"
|
"storage": "../database/database.sqlite"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
backend/index.js
152
backend/index.js
@@ -1,153 +1,7 @@
|
|||||||
const express = require('express');
|
const server = require('./server');
|
||||||
const RateLimit = require('express-rate-limit');
|
|
||||||
const path = require('path');
|
|
||||||
const { Sequelize } = require('sequelize');
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
// enable frontend access
|
|
||||||
const cors = require('cors');
|
|
||||||
const frontendOrigin = process.env.FRONTEND_ORIGIN || 'http://localhost:8000';
|
|
||||||
const corsOptions = {
|
|
||||||
origin: frontendOrigin,
|
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
|
||||||
};
|
|
||||||
app.use(cors(corsOptions));
|
|
||||||
|
|
||||||
// enable json middleware
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
// enable rate limiter
|
|
||||||
const limiter = RateLimit({
|
|
||||||
windowMs: 60 * 60 * 1000, // 1h
|
|
||||||
max: 1000, // 1000 requests per hour
|
|
||||||
message: 'Too many requests from this IP, please try again after an hour',
|
|
||||||
});
|
|
||||||
app.use(limiter);
|
|
||||||
|
|
||||||
// Specify the directory to serve static files
|
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
|
||||||
|
|
||||||
// init sequalize
|
|
||||||
const sequelize = new Sequelize({
|
|
||||||
dialect: 'sqlite',
|
|
||||||
storage: 'database/database.sqlite',
|
|
||||||
logging: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// "/"
|
|
||||||
const indexRoute = require('./routes/index');
|
|
||||||
app.use('/', indexRoute);
|
|
||||||
|
|
||||||
// "users"
|
|
||||||
const usersIndexRoute = require('./routes/users/index')(sequelize);
|
|
||||||
const usersFindRoute = require('./routes/users/find')(sequelize);
|
|
||||||
const usersSearchRoute = require('./routes/users/search')(sequelize);
|
|
||||||
const signUpRoute = require('./routes/users/signup')(sequelize);
|
|
||||||
const signInRoute = require('./routes/users/signin')(sequelize);
|
|
||||||
app.use('/users', usersIndexRoute);
|
|
||||||
app.use('/users', usersFindRoute);
|
|
||||||
app.use('/users', usersSearchRoute);
|
|
||||||
app.use('/users', signUpRoute);
|
|
||||||
app.use('/users', signInRoute);
|
|
||||||
// ESM import
|
|
||||||
(async () => {
|
|
||||||
const updateRoute = await import('./routes/users/update.mjs');
|
|
||||||
app.use('/users', updateRoute.default(sequelize));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// "/projects"
|
|
||||||
const projectsIndexRoute = require('./routes/projects/index')(sequelize);
|
|
||||||
const projectsShowRoute = require('./routes/projects/show')(sequelize);
|
|
||||||
const projectsNewRoute = require('./routes/projects/new')(sequelize);
|
|
||||||
const projectsEditRoute = require('./routes/projects/edit')(sequelize);
|
|
||||||
const projectsDeleteRoute = require('./routes/projects/delete')(sequelize);
|
|
||||||
app.use('/projects', projectsIndexRoute);
|
|
||||||
app.use('/projects', projectsShowRoute);
|
|
||||||
app.use('/projects', projectsNewRoute);
|
|
||||||
app.use('/projects', projectsEditRoute);
|
|
||||||
app.use('/projects', projectsDeleteRoute);
|
|
||||||
|
|
||||||
// "/folders"
|
|
||||||
const foldersIndexRoute = require('./routes/folders/index')(sequelize);
|
|
||||||
const foldersNewRoute = require('./routes/folders/new')(sequelize);
|
|
||||||
const foldersEditRoute = require('./routes/folders/edit')(sequelize);
|
|
||||||
const foldersDeleteRoute = require('./routes/folders/delete')(sequelize);
|
|
||||||
app.use('/folders', foldersIndexRoute);
|
|
||||||
app.use('/folders', foldersNewRoute);
|
|
||||||
app.use('/folders', foldersEditRoute);
|
|
||||||
app.use('/folders', foldersDeleteRoute);
|
|
||||||
|
|
||||||
// "/cases"
|
|
||||||
const casesDownloadRoute = require('./routes/cases/download')(sequelize);
|
|
||||||
const casesIndexRoute = require('./routes/cases/index')(sequelize);
|
|
||||||
const casesIndexByProjectIdRoute = require('./routes/cases/indexByProjectId')(sequelize);
|
|
||||||
const casesShowRoute = require('./routes/cases/show')(sequelize);
|
|
||||||
const casesNewRoute = require('./routes/cases/new')(sequelize);
|
|
||||||
const casesEditRoute = require('./routes/cases/edit')(sequelize);
|
|
||||||
const casesDeleteRoute = require('./routes/cases/delete')(sequelize);
|
|
||||||
app.use('/cases', casesDownloadRoute);
|
|
||||||
app.use('/cases', casesIndexRoute);
|
|
||||||
app.use('/cases', casesIndexByProjectIdRoute);
|
|
||||||
app.use('/cases', casesShowRoute);
|
|
||||||
app.use('/cases', casesNewRoute);
|
|
||||||
app.use('/cases', casesEditRoute);
|
|
||||||
app.use('/cases', casesDeleteRoute);
|
|
||||||
|
|
||||||
// "/steps"
|
|
||||||
const stepsEditRoute = require('./routes/steps/edit')(sequelize);
|
|
||||||
app.use('/steps', stepsEditRoute);
|
|
||||||
|
|
||||||
// "/attachments"
|
|
||||||
const attachmentsNewRoute = require('./routes/attachments/new')(sequelize);
|
|
||||||
const attachmentsDeleteRoute = require('./routes/attachments/delete')(sequelize);
|
|
||||||
const attachmentsDownloadRoute = require('./routes/attachments/download')(sequelize);
|
|
||||||
app.use('/attachments', attachmentsNewRoute);
|
|
||||||
app.use('/attachments', attachmentsDeleteRoute);
|
|
||||||
app.use('/attachments', attachmentsDownloadRoute);
|
|
||||||
|
|
||||||
// "/runs"
|
|
||||||
const runsDownloadRoute = require('./routes/runs/download')(sequelize);
|
|
||||||
const runsIndexRoute = require('./routes/runs/index')(sequelize);
|
|
||||||
const runsShowRoute = require('./routes/runs/show')(sequelize);
|
|
||||||
const runsNewRoute = require('./routes/runs/new')(sequelize);
|
|
||||||
const runsEditRoute = require('./routes/runs/edit')(sequelize);
|
|
||||||
const runDeleteRoute = require('./routes/runs/delete')(sequelize);
|
|
||||||
app.use('/runs', runsDownloadRoute);
|
|
||||||
app.use('/runs', runsIndexRoute);
|
|
||||||
app.use('/runs', runsShowRoute);
|
|
||||||
app.use('/runs', runsNewRoute);
|
|
||||||
app.use('/runs', runsEditRoute);
|
|
||||||
app.use('/runs', runDeleteRoute);
|
|
||||||
|
|
||||||
// "/runcases"
|
|
||||||
const runCaseIndexRoute = require('./routes/runcases/index')(sequelize);
|
|
||||||
const runCaseEditRoute = require('./routes/runcases/edit')(sequelize);
|
|
||||||
app.use('/runcases', runCaseIndexRoute);
|
|
||||||
app.use('/runcases', runCaseEditRoute);
|
|
||||||
|
|
||||||
// "/members"
|
|
||||||
const membersIndexRoute = require('./routes/members/index')(sequelize);
|
|
||||||
const membersNewRoute = require('./routes/members/new')(sequelize);
|
|
||||||
const membersEditRoute = require('./routes/members/edit')(sequelize);
|
|
||||||
const membersDeleteRoute = require('./routes/members/delete')(sequelize);
|
|
||||||
const membersCheckRoute = require('./routes/members/check')(sequelize);
|
|
||||||
app.use('/members', membersIndexRoute);
|
|
||||||
app.use('/members', membersNewRoute);
|
|
||||||
app.use('/members', membersEditRoute);
|
|
||||||
app.use('/members', membersDeleteRoute);
|
|
||||||
app.use('/members', membersCheckRoute);
|
|
||||||
|
|
||||||
// "/home"
|
|
||||||
const homeIndexRoute = require('./routes/home/index')(sequelize);
|
|
||||||
app.use('/home', homeIndexRoute);
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8001;
|
const PORT = process.env.PORT || 8001;
|
||||||
app.listen(PORT, () => {
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
console.log(`Backend server is running on port ${PORT}`);
|
console.log(`Backend server is running on port ${PORT}`);
|
||||||
console.log(`Access from the frontend origin: ${frontendOrigin} is valid.`);
|
|
||||||
if (!process.env.SECRET_KEY) {
|
|
||||||
console.log(
|
|
||||||
"[Warning]: Default key is used for token generation. Please set the environment variable 'SECRET_KEY'`."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --env-file=.env index",
|
"start": "node --env-file=.env index",
|
||||||
"dev": "node index",
|
"dev": "node index",
|
||||||
"migrate": "bash scripts/migrate.sh",
|
"migrate": "sequelize db:migrate",
|
||||||
"seed": "bash scripts/seed.sh",
|
"seed": "sequelize db:seed:all",
|
||||||
"drop": "bash scripts/drop.sh"
|
"drop": "sequelize db:migrate:undo:all"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
npx sequelize db:migrate:undo:all
|
|
||||||
echo "Dropped all tables"
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
npx sequelize db:migrate
|
|
||||||
echo "Migration complete."
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
npx sequelize db:seed:all
|
|
||||||
143
backend/server.js
Normal file
143
backend/server.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const RateLimit = require('express-rate-limit');
|
||||||
|
const path = require('path');
|
||||||
|
const { Sequelize } = require('sequelize');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// enable json middleware
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// enable rate limiter
|
||||||
|
const limiter = RateLimit({
|
||||||
|
windowMs: 60 * 60 * 1000, // 1h
|
||||||
|
max: 1000, // 1000 requests per hour
|
||||||
|
message: 'Too many requests from this IP, please try again after an hour',
|
||||||
|
});
|
||||||
|
app.use(limiter);
|
||||||
|
|
||||||
|
// Specify the directory to serve static files
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
// init sequalize
|
||||||
|
const sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: 'database/database.sqlite',
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// "/"
|
||||||
|
const indexRoute = require('./routes/index');
|
||||||
|
app.use('/', indexRoute);
|
||||||
|
|
||||||
|
// "users"
|
||||||
|
const usersIndexRoute = require('./routes/users/index')(sequelize);
|
||||||
|
const usersFindRoute = require('./routes/users/find')(sequelize);
|
||||||
|
const usersSearchRoute = require('./routes/users/search')(sequelize);
|
||||||
|
const signUpRoute = require('./routes/users/signup')(sequelize);
|
||||||
|
const signInRoute = require('./routes/users/signin')(sequelize);
|
||||||
|
app.use('/users', usersIndexRoute);
|
||||||
|
app.use('/users', usersFindRoute);
|
||||||
|
app.use('/users', usersSearchRoute);
|
||||||
|
app.use('/users', signUpRoute);
|
||||||
|
app.use('/users', signInRoute);
|
||||||
|
// ESM import
|
||||||
|
(async () => {
|
||||||
|
const updateRoute = await import('./routes/users/update.mjs');
|
||||||
|
app.use('/users', updateRoute.default(sequelize));
|
||||||
|
})();
|
||||||
|
|
||||||
|
// "/projects"
|
||||||
|
const projectsIndexRoute = require('./routes/projects/index')(sequelize);
|
||||||
|
const projectsShowRoute = require('./routes/projects/show')(sequelize);
|
||||||
|
const projectsNewRoute = require('./routes/projects/new')(sequelize);
|
||||||
|
const projectsEditRoute = require('./routes/projects/edit')(sequelize);
|
||||||
|
const projectsDeleteRoute = require('./routes/projects/delete')(sequelize);
|
||||||
|
app.use('/projects', projectsIndexRoute);
|
||||||
|
app.use('/projects', projectsShowRoute);
|
||||||
|
app.use('/projects', projectsNewRoute);
|
||||||
|
app.use('/projects', projectsEditRoute);
|
||||||
|
app.use('/projects', projectsDeleteRoute);
|
||||||
|
|
||||||
|
// "/folders"
|
||||||
|
const foldersIndexRoute = require('./routes/folders/index')(sequelize);
|
||||||
|
const foldersNewRoute = require('./routes/folders/new')(sequelize);
|
||||||
|
const foldersEditRoute = require('./routes/folders/edit')(sequelize);
|
||||||
|
const foldersDeleteRoute = require('./routes/folders/delete')(sequelize);
|
||||||
|
app.use('/folders', foldersIndexRoute);
|
||||||
|
app.use('/folders', foldersNewRoute);
|
||||||
|
app.use('/folders', foldersEditRoute);
|
||||||
|
app.use('/folders', foldersDeleteRoute);
|
||||||
|
|
||||||
|
// "/cases"
|
||||||
|
// "/cases"
|
||||||
|
const casesDownloadRoute = require('./routes/cases/download')(sequelize);
|
||||||
|
const casesIndexRoute = require('./routes/cases/index')(sequelize);
|
||||||
|
const casesIndexByProjectIdRoute = require('./routes/cases/indexByProjectId')(sequelize);
|
||||||
|
const casesShowRoute = require('./routes/cases/show')(sequelize);
|
||||||
|
const casesNewRoute = require('./routes/cases/new')(sequelize);
|
||||||
|
const casesEditRoute = require('./routes/cases/edit')(sequelize);
|
||||||
|
const casesDeleteRoute = require('./routes/cases/delete')(sequelize);
|
||||||
|
app.use('/cases', casesDownloadRoute);
|
||||||
|
app.use('/cases', casesIndexRoute);
|
||||||
|
app.use('/cases', casesIndexByProjectIdRoute);
|
||||||
|
app.use('/cases', casesShowRoute);
|
||||||
|
app.use('/cases', casesNewRoute);
|
||||||
|
app.use('/cases', casesEditRoute);
|
||||||
|
app.use('/cases', casesDeleteRoute);
|
||||||
|
|
||||||
|
// "/steps"
|
||||||
|
const stepsEditRoute = require('./routes/steps/edit')(sequelize);
|
||||||
|
app.use('/steps', stepsEditRoute);
|
||||||
|
|
||||||
|
// "/attachments"
|
||||||
|
const attachmentsNewRoute = require('./routes/attachments/new')(sequelize);
|
||||||
|
const attachmentsDeleteRoute = require('./routes/attachments/delete')(sequelize);
|
||||||
|
const attachmentsDownloadRoute = require('./routes/attachments/download')(sequelize);
|
||||||
|
app.use('/attachments', attachmentsNewRoute);
|
||||||
|
app.use('/attachments', attachmentsDeleteRoute);
|
||||||
|
app.use('/attachments', attachmentsDownloadRoute);
|
||||||
|
|
||||||
|
// "/runs"
|
||||||
|
const runsDownloadRoute = require('./routes/runs/download')(sequelize);
|
||||||
|
const runsIndexRoute = require('./routes/runs/index')(sequelize);
|
||||||
|
const runsShowRoute = require('./routes/runs/show')(sequelize);
|
||||||
|
const runsNewRoute = require('./routes/runs/new')(sequelize);
|
||||||
|
const runsEditRoute = require('./routes/runs/edit')(sequelize);
|
||||||
|
const runDeleteRoute = require('./routes/runs/delete')(sequelize);
|
||||||
|
app.use('/runs', runsDownloadRoute);
|
||||||
|
app.use('/runs', runsIndexRoute);
|
||||||
|
app.use('/runs', runsShowRoute);
|
||||||
|
app.use('/runs', runsNewRoute);
|
||||||
|
app.use('/runs', runsEditRoute);
|
||||||
|
app.use('/runs', runDeleteRoute);
|
||||||
|
|
||||||
|
// "/runcases"
|
||||||
|
const runCaseIndexRoute = require('./routes/runcases/index')(sequelize);
|
||||||
|
const runCaseEditRoute = require('./routes/runcases/edit')(sequelize);
|
||||||
|
app.use('/runcases', runCaseIndexRoute);
|
||||||
|
app.use('/runcases', runCaseEditRoute);
|
||||||
|
|
||||||
|
// "/members"
|
||||||
|
const membersIndexRoute = require('./routes/members/index')(sequelize);
|
||||||
|
const membersNewRoute = require('./routes/members/new')(sequelize);
|
||||||
|
const membersEditRoute = require('./routes/members/edit')(sequelize);
|
||||||
|
const membersDeleteRoute = require('./routes/members/delete')(sequelize);
|
||||||
|
const membersCheckRoute = require('./routes/members/check')(sequelize);
|
||||||
|
app.use('/members', membersIndexRoute);
|
||||||
|
app.use('/members', membersNewRoute);
|
||||||
|
app.use('/members', membersEditRoute);
|
||||||
|
app.use('/members', membersDeleteRoute);
|
||||||
|
app.use('/members', membersCheckRoute);
|
||||||
|
|
||||||
|
// "/home"
|
||||||
|
const homeIndexRoute = require('./routes/home/index')(sequelize);
|
||||||
|
app.use('/home', homeIndexRoute);
|
||||||
|
|
||||||
|
if (!process.env.SECRET_KEY) {
|
||||||
|
console.log(
|
||||||
|
"[Warning]: Default key is used for token generation. Please set the environment variable 'SECRET_KEY'`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the app instead of starting the server
|
||||||
|
module.exports = app;
|
||||||
87
combined_server.js
Normal file
87
combined_server.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Use express from backend node_modules
|
||||||
|
const express = require('./backend/node_modules/express');
|
||||||
|
const { createServer: createHttpServer } = require('http');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
async function runMigrations() {
|
||||||
|
try {
|
||||||
|
console.log('Running database migrations...');
|
||||||
|
// Use execSync with cwd option to run in the backend directory
|
||||||
|
execSync('npx sequelize-cli db:migrate', {
|
||||||
|
cwd: path.join(__dirname, 'backend'),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Database migrations completed successfully.');
|
||||||
|
|
||||||
|
if (process.env.IS_DEMO === 'true' || process.env.IS_DEMO === '1') {
|
||||||
|
console.log('Demo mode detected. Seeding the database...');
|
||||||
|
execSync('npx sequelize-cli db:seed:all', {
|
||||||
|
cwd: path.join(__dirname, 'backend'),
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
console.log('Database seeding completed successfully.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error running database migrations or seeding:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startServer() {
|
||||||
|
try {
|
||||||
|
const server = express();
|
||||||
|
const httpServer = createHttpServer(server);
|
||||||
|
|
||||||
|
// Import the backend app
|
||||||
|
const backendApp = require('./backend/server');
|
||||||
|
|
||||||
|
// Use the backend app for /api routes
|
||||||
|
server.use('/api', backendApp);
|
||||||
|
|
||||||
|
// For Next.js standalone build
|
||||||
|
// Check if we have the Next.js server file
|
||||||
|
const nextServerPath = './node_modules/next/dist/server/next.js';
|
||||||
|
if (fs.existsSync(nextServerPath)) {
|
||||||
|
// Import Next.js
|
||||||
|
const next = require(nextServerPath);
|
||||||
|
|
||||||
|
// Initialize Next.js app
|
||||||
|
const dev = process.env.NODE_ENV !== 'production';
|
||||||
|
const nextApp = next({ dev, dir: path.join(__dirname, '.') });
|
||||||
|
const handle = nextApp.getRequestHandler();
|
||||||
|
|
||||||
|
// Prepare Next.js
|
||||||
|
nextApp.prepare().then(() => {
|
||||||
|
console.log('nextjs prepared');
|
||||||
|
// Use Next.js to handle all other routes
|
||||||
|
server.all('*', (req, res) => {
|
||||||
|
return handle(req, res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Next.js module not found at:', nextServerPath);
|
||||||
|
server.all('*', (req, res) => {
|
||||||
|
res.status(500).send('Frontend server not available');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8000;
|
||||||
|
httpServer.listen(PORT, (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log(`> Ready on http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting server:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runMigrations().then(() => {
|
||||||
|
startServer();
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Failed to start application:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,31 +1,16 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
unittcms:
|
||||||
image: unittcms-backend:latest
|
image: unittcms:latest
|
||||||
build:
|
|
||||||
context: ./backend
|
|
||||||
ports:
|
|
||||||
- '8001:8001'
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- FRONTEND_ORIGIN=http://localhost:8000
|
|
||||||
- PORT=8001
|
|
||||||
volumes:
|
|
||||||
- db-data:/app/database
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
image: unittcms-frontend:latest
|
|
||||||
build:
|
|
||||||
context: ./frontend
|
|
||||||
ports:
|
ports:
|
||||||
- '8000:8000'
|
- '8000:8000'
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
|
||||||
- NEXT_PUBLIC_BACKEND_ORIGIN=http://localhost:8001
|
|
||||||
- PORT=8000
|
- PORT=8000
|
||||||
depends_on:
|
- SECRET_KEY=your_secret_key_here
|
||||||
- backend
|
- IS_DEMO=false # set to true to seed the database
|
||||||
|
volumes:
|
||||||
|
- db-data:/app/database
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db-data:
|
db-data:
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
FROM node:20-alpine AS base
|
|
||||||
|
|
||||||
# Install dependencies only when needed
|
|
||||||
FROM base AS deps
|
|
||||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install dependencies based on the preferred package manager
|
|
||||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
|
||||||
RUN \
|
|
||||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
|
||||||
elif [ -f package-lock.json ]; then npm ci; \
|
|
||||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
|
||||||
else echo "Lockfile not found." && exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Rebuild the source code only when needed
|
|
||||||
FROM base AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Next.js collects completely anonymous telemetry data about general usage.
|
|
||||||
# Learn more here: https://nextjs.org/telemetry
|
|
||||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
|
||||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
if [ -f yarn.lock ]; then yarn run build; \
|
|
||||||
elif [ -f package-lock.json ]; then npm run build; \
|
|
||||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
|
||||||
else echo "Lockfile not found." && exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Production image, copy all the files and run next
|
|
||||||
FROM base AS runner
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
|
||||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
COPY --from=builder /app/public ./public
|
|
||||||
|
|
||||||
# Set the correct permission for prerender cache
|
|
||||||
RUN mkdir .next
|
|
||||||
RUN chown nextjs:nodejs .next
|
|
||||||
|
|
||||||
# Automatically leverage output traces to reduce image size
|
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
||||||
|
|
||||||
USER nextjs
|
|
||||||
|
|
||||||
EXPOSE 8000
|
|
||||||
|
|
||||||
# server.js is created by next build from the standalone output
|
|
||||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
|
||||||
CMD HOSTNAME="0.0.0.0" node server.js
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const Config = {
|
const Config = {
|
||||||
apiServer: process.env.NEXT_PUBLIC_BACKEND_ORIGIN || 'http://localhost:8001',
|
apiServer: process.env.NEXT_PUBLIC_BACKEND_ORIGIN || '/api',
|
||||||
|
|
||||||
// set 'NEXT_PUBLIC_IS_DEMO=true' in frontend/.env
|
// set 'NEXT_PUBLIC_IS_DEMO=true' in frontend/.env
|
||||||
isDemoSite: process.env.NEXT_PUBLIC_IS_DEMO === 'true' || false,
|
isDemoSite: process.env.NEXT_PUBLIC_IS_DEMO === 'true' || false,
|
||||||
|
|||||||
Reference in New Issue
Block a user