infra: fix docker issues

This commit is contained in:
isra el
2025-03-30 09:59:49 +03:00
parent 1abbdcd641
commit 4c77b967ff
10 changed files with 296 additions and 91 deletions

View File

@@ -1,11 +1,16 @@
PORT=3005
MONGO_URI=mongodb://textbeeUser:textbeePassword@mongo:27017/TextBee
JWT_SECRET=secret
PORT=3001
MONGO_URI=mongodb://adminUser:adminPassword@textbee-db:27017/textbee?authSource=admin
# to setup initial password
MONGO_ROOT_USER=adminUser
MONGO_ROOT_PASS=adminPassword
JWT_SECRET=secret # change this to a secure random string
JWT_EXPIRATION=60d
FRONTEND_URL=http://localhost:3000
#Update from Firebase json file
#Update from Firebase service account json file
FIREBASE_PROJECT_ID=
FIREBASE_PRIVATE_KEY_ID=
FIREBASE_PRIVATE_KEY=
@@ -18,7 +23,7 @@ MAIL_PORT=
MAIL_USER=
MAIL_PASS=
MAIL_FROM=
MAIL_REPLY_TO=textbee.dev@gmail.com
MAIL_REPLY_TO=
# SMS Queue Configuration
USE_SMS_QUEUE=false

View File

@@ -1,25 +1,61 @@
FROM node:18-alpine AS base
RUN npm i -g pnpm
# Stage 1: Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy package.json and pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./
RUN pnpm i
# Install dependencies
RUN pnpm install --frozen-lockfile
# Stage 2: Builder
FROM node:22-alpine AS builder
WORKDIR /app
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
FROM base AS dev
ENV NODE_ENV=development
ENTRYPOINT ["pnpm", "start:dev"]
FROM base AS build
ENV NODE_ENV=production
# Build the application
RUN pnpm build
FROM node:18-alpine AS prod
ENV NODE_ENV=production
EXPOSE 3005
# Stage 3: Production
FROM node:22-alpine AS runner
WORKDIR /app
RUN npm i -g pnpm
COPY --from=build /app/.env ./.env
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json /app/pnpm-lock.yaml ./
RUN pnpm i --prod
ENTRYPOINT ["pnpm", "start"]
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Set NODE_ENV to production
ENV NODE_ENV production
# Copy necessary files for production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
# Install only production dependencies
RUN pnpm install --prod --frozen-lockfile
# Add a non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nestjs && \
chown -R nestjs:nodejs /app
USER nestjs
# Expose the port specified by the PORT environment variable (default: 3001)
ENV PORT 300
EXPOSE ${PORT}
# Health check to verify app is running
# HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
# CMD wget -q -O - http://localhost:${PORT}/api/v1/health || exit 1
# Command to run the application
CMD ["node", "dist/main"]

View File

@@ -4,11 +4,11 @@ import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import * as firebase from 'firebase-admin'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import * as express from 'express';
import * as express from 'express'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const PORT = process.env.PORT || 3005
const PORT = process.env.PORT || 3001
app.setGlobalPrefix('api')
app.enableVersioning({
@@ -51,7 +51,10 @@ async function bootstrap() {
credential: firebase.credential.cert(firebaseConfig),
})
app.use('/api/v1/billing/webhook/polar', express.raw({ type: 'application/json' }));
app.use(
'/api/v1/billing/webhook/polar',
express.raw({ type: 'application/json' }),
)
app.enableCors()
await app.listen(PORT)
}

View File

@@ -14,7 +14,7 @@ export class User {
@Prop({ type: String, required: true, unique: true, lowercase: true })
email: string
@Prop({ type: String, unique: true })
@Prop({ type: String, unique: true, sparse: true })
googleId?: string
@Prop({ type: String })

View File

@@ -1,56 +1,141 @@
services:
web:
container_name: web
build:
context: ./web
dockerfile: Dockerfile
ports:
- "3000:3000"
depends_on:
- mongo
environment:
NODE_ENV: production
api:
container_name: api
services:
# MongoDB service
textbee-db:
container_name: textbee-db
image: mongo:latest
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USER:-adminUser}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASS:-adminPassword}
- MONGO_INITDB_DATABASE=textbee
# - MONGO_DB_USERNAME=${MONGO_USER:-textbeeUser}
# - MONGO_DB_PASSWORD=${MONGO_PASS:-textbeePassword}
volumes:
# - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
- mongodb_data:/data/db
ports:
# only allow access from the same machine, and use port 27018 to avoid conflict with default mongo port 27017
# - "127.0.0.1:${MONGO_PORT:-27018}:27017"
- "${MONGO_PORT:-27018}:27017"
networks:
- textbee-network
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# MongoDB Express (optional admin UI)
mongo-express:
container_name: textbee-mongo-express
image: mongo-express:latest
restart: always
ports:
- "${MONGO_EXPRESS_PORT:-8081}:8081"
environment:
- ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_ROOT_USER:-adminUser}
- ME_CONFIG_MONGODB_ADMINPASSWORD=${MONGO_ROOT_PASS:-adminPassword}
- ME_CONFIG_MONGODB_SERVER=textbee-db
depends_on:
textbee-db:
condition: service_healthy
networks:
- textbee-network
# NestJS API
textbee-api:
container_name: textbee-api
build:
context: ./api
dockerfile: Dockerfile
target: prod
restart: always
ports:
- "3005:3005"
depends_on:
- mongo
- "${PORT:-3001}:3001"
env_file:
- ./api/.env
environment:
NODE_ENV: production
- PORT=${PORT:-3001}
# - MONGO_URI=${MONGO_URI:-mongodb://${MONGO_USER:-textbeeUser}:${MONGO_PASS:-textbeePassword}@textbee-db:27018/TextBee}
# - MONGO_URI=mongodb://adminUser:adminPassword@textbee-db:27018/textbee
# - FRONTEND_URL=${NEXT_PUBLIC_SITE_URL:-http://localhost:3000}
# - JWT_SECRET=${JWT_SECRET:-your_jwt_secret_here}
# - JWT_EXPIRATION=${JWT_EXPIRATION:-60d}
# - MAIL_HOST=${MAIL_HOST}
# - MAIL_PORT=${MAIL_PORT}
# - MAIL_USER=${MAIL_USER}
# - MAIL_PASS=${MAIL_PASS}
# - MAIL_FROM=${MAIL_FROM}
# - USE_SMS_QUEUE=${USE_SMS_QUEUE:-false}
# - REDIS_HOST=${REDIS_HOST:-redis}
# - REDIS_PORT=${REDIS_PORT:-6379}
# - FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID}
# - FIREBASE_PRIVATE_KEY_ID=${FIREBASE_PRIVATE_KEY_ID}
# - FIREBASE_PRIVATE_KEY=${FIREBASE_PRIVATE_KEY}
# - FIREBASE_CLIENT_EMAIL=${FIREBASE_CLIENT_EMAIL}
# - FIREBASE_CLIENT_ID=${FIREBASE_CLIENT_ID}
# - FIREBASE_CLIENT_C509_CERT_URL=${FIREBASE_CLIENT_C509_CERT_URL}
depends_on:
textbee-db:
condition: service_healthy
networks:
- textbee-network
mongo:
container_name: mongo
image: mongo
# Next.js Web
textbee-web:
container_name: textbee-web
build:
context: ./web
dockerfile: Dockerfile
restart: always
ports:
- "27017:27017"
- "${PORT:-3000}:3000"
env_file:
- ./web/.env
environment:
MONGO_INITDB_ROOT_USERNAME: adminUser
MONGO_INITDB_ROOT_PASSWORD: adminPassword
MONGO_INITDB_DATABASE: TextBee
volumes:
- textbee-db-data:/data/db
# THe following scripts creates TextBee DB automatically, also the user which web and api are connecting with.
- ./mongo-init:/docker-entrypoint-initdb.d:ro
mongo-express:
container_name: mongo-ee
image: mongo-express
restart: always
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: adminUser
ME_CONFIG_MONGODB_ADMINPASSWORD: adminPassword
ME_CONFIG_MONGODB_URL: mongodb://adminUser:adminPassword@mongo:27017/
ME_CONFIG_BASICAUTH: "false"
- PORT=${PORT:-3000}
# - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-http://localhost:3000}
- NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL:-http://localhost:3001/api/v1}
# - AUTH_SECRET=${AUTH_SECRET:-generate_a_secure_random_string_here}
# - DATABASE_URL=mongodb://adminUser:adminPassword@textbee-db:27018/textbee
# - DATABASE_URL=mongodb://adminUser:adminPassword@textbee-db:27018/textbee
# - MAIL_HOST=${MAIL_HOST}
# - MAIL_PORT=${MAIL_PORT}
# - MAIL_USER=${MAIL_USER}
# - MAIL_PASS=${MAIL_PASS}
# - MAIL_FROM=${MAIL_FROM}
# - ADMIN_EMAIL=${ADMIN_EMAIL}
# - NEXT_PUBLIC_GOOGLE_CLIENT_ID=${NEXT_PUBLIC_GOOGLE_CLIENT_ID}
# - NEXT_PUBLIC_TAWKTO_EMBED_URL=${NEXT_PUBLIC_TAWKTO_EMBED_URL}
depends_on:
- mongo
- textbee-api
networks:
- textbee-network
# Redis (if SMS queue is needed)
redis:
container_name: textbee-redis
image: redis:alpine
restart: always
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
networks:
- textbee-network
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
networks:
textbee-network:
driver: bridge
volumes:
textbee-db-data:
mongodb_data:
redis_data:

2
web/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
.git

View File

@@ -1,5 +1,5 @@
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_API_BASE_URL=http://localhost:3005/api/v1
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api/v1
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
NEXT_PUBLIC_TAWKTO_EMBED_URL=

View File

@@ -1,20 +1,82 @@
FROM node:18-alpine AS base
# Stage 1: Install web dependencies
FROM base AS web-deps
# Stage 1: Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY .env ./.env
# Install pnpm and required OpenSSL dependencies
RUN apk add --no-cache openssl openssl-dev
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy package.json and pnpm-lock.yaml
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Stage 2: Build the web application
FROM base AS web-builder
ENV NODE_ENV=production
EXPOSE 3000
# Install dependencies
RUN pnpm install --frozen-lockfile
# Stage 2: Builder
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=web-deps /app/node_modules ./node_modules
COPY --from=web-deps /app/.env .env
RUN corepack enable pnpm && pnpm run vercel-build
CMD ["pnpm", "start"]
# Install pnpm and required OpenSSL dependencies
RUN apk add --no-cache openssl openssl-dev
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy all files
COPY . .
# Set environment variables for building
ENV NEXT_TELEMETRY_DISABLED 1
# Generate prisma client - make sure it exists
RUN pnpm prisma generate
# Build the application
RUN pnpm build
# Stage 3: Production runner
FROM node:22-alpine AS runner
WORKDIR /app
# Install pnpm and required OpenSSL dependencies
RUN apk add --no-cache openssl openssl-dev
RUN corepack enable && corepack prepare pnpm@latest --activate
# Set environment variables
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV CONTAINER_RUNTIME docker
# Add a non-root user to run the app
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs && \
chown -R nextjs:nodejs /app
# Copy necessary files for the standalone app
COPY --from=builder --chown=nextjs:nodejs /app/next.config.js ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Copy Prisma schema and generate client during runtime
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
COPY --from=builder --chown=nextjs:nodejs /app/pnpm-lock.yaml ./
# Install only production dependencies, including Prisma, and generate Prisma client
RUN pnpm install --prod --frozen-lockfile && \
pnpm prisma generate
# Switch to non-root user
USER nextjs
# Expose the port the app will run on
ENV PORT 3000
EXPOSE ${PORT}
# Health check to verify app is running
# HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
# CMD wget -q -O - http://localhost:${PORT}/api/health || exit 1
CMD ["node", "server.js"]

View File

@@ -1,5 +1,5 @@
import CredentialsProvider from 'next-auth/providers/credentials'
import httpBrowserClient from './httpBrowserClient'
import { httpServerClient } from './httpServerClient'
import { DefaultSession } from 'next-auth'
import { ApiEndpoints } from '@/config/api'
import { Routes } from '@/config/routes'
@@ -31,7 +31,7 @@ export const authOptions = {
async authorize(credentials) {
const { email, password } = credentials
try {
const res = await httpBrowserClient.post(ApiEndpoints.auth.login(), {
const res = await httpServerClient.post(ApiEndpoints.auth.login(), {
email,
password,
})
@@ -62,7 +62,7 @@ export const authOptions = {
async authorize(credentials) {
const { email, password, name, phone } = credentials
try {
const res = await httpBrowserClient.post(
const res = await httpServerClient.post(
ApiEndpoints.auth.register(),
{
email,
@@ -94,7 +94,7 @@ export const authOptions = {
async authorize(credentials) {
const { idToken } = credentials
try {
const res = await httpBrowserClient.post(
const res = await httpServerClient.post(
ApiEndpoints.auth.signInWithGoogle(),
{
idToken,

View File

@@ -3,8 +3,20 @@ import { getServerSession } from 'next-auth/next'
import { authOptions } from '@/lib/auth'
import { Session } from 'next-auth'
// Create a base URL that works in Docker container network if running in a container
// or falls back to the public URL if not in a container
const getServerSideBaseUrl = () => {
// When running server-side in Docker, use the service name from docker-compose
if (process.env.CONTAINER_RUNTIME === 'docker') {
console.log('Running in Docker container')
return 'http://textbee-api:3001/api/v1'
}
// Otherwise use the public URL
return process.env.NEXT_PUBLIC_API_BASE_URL || ''
}
export const httpServerClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || '',
baseURL: getServerSideBaseUrl(),
})
httpServerClient.interceptors.request.use(async (config) => {