Express migration (#80)
* Initial migration * Cleanup and create migration scripts * Introduce test suite * Fix test issues * Correct CORS issue and update paths * Update README
This commit is contained in:
parent
7a5fe2b11c
commit
3c1209a5a9
167 changed files with 24985 additions and 9335 deletions
232
Dockerfile
232
Dockerfile
|
|
@ -1,108 +1,156 @@
|
|||
# Use a base image that supports both Node.js and Ruby
|
||||
FROM ruby:3.2.2-slim
|
||||
# ============================================================================
|
||||
# Ultra-optimized multi-stage build for minimal rootless Docker image
|
||||
# ============================================================================
|
||||
|
||||
# Install Node.js and necessary packages
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libsqlite3-dev \
|
||||
openssl \
|
||||
libffi-dev \
|
||||
libpq-dev \
|
||||
curl \
|
||||
gnupg2 \
|
||||
ca-certificates && \
|
||||
# Install Node.js 20
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get clean
|
||||
# Stage 1: Frontend Build Environment (optimized)
|
||||
FROM node:20-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
WORKDIR /app
|
||||
|
||||
# Install Ruby dependencies first
|
||||
COPY Gemfile* ./
|
||||
RUN bundle config set --local deployment 'true' && \
|
||||
bundle config set --local without 'development test' && \
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
# Install Node.js dependencies
|
||||
# Copy frontend package files
|
||||
COPY package*.json ./
|
||||
COPY webpack.config.js ./
|
||||
COPY babel.config.js ./
|
||||
COPY tsconfig.json ./
|
||||
COPY postcss.config.js ./
|
||||
COPY tailwind.config.js ./
|
||||
RUN npm ci
|
||||
COPY webpack.config.js babel.config.js tsconfig.json postcss.config.js tailwind.config.js ./
|
||||
|
||||
# Remove any existing development databases
|
||||
RUN rm -f db/development*
|
||||
# Install frontend dependencies (including dev deps for build)
|
||||
RUN npm install --ignore-scripts --no-audit --no-fund && \
|
||||
npm cache clean --force && \
|
||||
rm -rf ~/.npm
|
||||
|
||||
# Copy application files
|
||||
COPY app/ app/
|
||||
COPY config/ config/
|
||||
COPY config.ru ./
|
||||
COPY Rakefile ./
|
||||
COPY app.rb ./
|
||||
COPY db/migrate/ db/migrate/
|
||||
COPY db/schema.rb db/schema.rb
|
||||
# Copy frontend source code
|
||||
COPY frontend/ frontend/
|
||||
COPY public/ public/
|
||||
COPY src/ src/
|
||||
|
||||
# Create non-root user for security
|
||||
RUN useradd -m -U app && \
|
||||
chown -R app:app /usr/src/app
|
||||
# Build frontend assets with optimizations
|
||||
RUN NODE_ENV=production npm run build && \
|
||||
# Remove source maps and dev artifacts
|
||||
find dist -name "*.map" -delete && \
|
||||
find dist -name "*.dev.*" -delete && \
|
||||
# Compress built assets
|
||||
find dist -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) -exec gzip -9 -k {} \;
|
||||
|
||||
# Stage 2: Backend Dependencies (ultra-minimal)
|
||||
FROM node:20-alpine AS backend-deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies temporarily for native modules
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
sqlite-dev
|
||||
|
||||
# Install only runtime dependencies for backend
|
||||
COPY backend/package*.json ./
|
||||
RUN npm install --production --no-audit --no-fund && \
|
||||
npm cache clean --force && \
|
||||
rm -rf ~/.npm /tmp/* && \
|
||||
# Remove build dependencies after install
|
||||
apk del .build-deps && \
|
||||
# Remove unnecessary files from node_modules
|
||||
find node_modules -name "*.md" -delete && \
|
||||
find node_modules -name "*.txt" -delete && \
|
||||
find node_modules -name "LICENSE*" -delete && \
|
||||
find node_modules -name "CHANGELOG*" -delete && \
|
||||
find node_modules -name "README*" -delete && \
|
||||
find node_modules -name ".github" -type d -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find node_modules -name "test" -type d -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find node_modules -name "tests" -type d -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find node_modules -name "docs" -type d -exec rm -rf {} + 2>/dev/null || true && \
|
||||
find node_modules -name "examples" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
# Stage 3: Test Stage (run tests before production)
|
||||
FROM node:20-alpine AS test
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies for testing
|
||||
RUN apk add --no-cache --virtual .test-deps \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
sqlite-dev
|
||||
|
||||
# Copy backend package files and install all dependencies (including dev)
|
||||
COPY backend/package*.json ./backend/
|
||||
RUN cd backend && npm install --no-audit --no-fund
|
||||
|
||||
# Copy backend source code
|
||||
COPY backend/ ./backend/
|
||||
|
||||
# Run tests
|
||||
RUN cd backend && npm test
|
||||
|
||||
# Stage 4: Final Production Image (minimal base)
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
# Create non-root user first (before installing packages)
|
||||
RUN addgroup -g 1001 -S app && \
|
||||
adduser -S app -u 1001 -G app
|
||||
|
||||
# Install minimal runtime dependencies with size optimization
|
||||
RUN apk add --no-cache --virtual .runtime-deps \
|
||||
sqlite \
|
||||
openssl \
|
||||
curl \
|
||||
dumb-init && \
|
||||
# Clean up package cache immediately
|
||||
rm -rf /var/cache/apk/* /tmp/* && \
|
||||
# Remove unnecessary files
|
||||
rm -rf /usr/share/man /usr/share/doc /usr/share/info
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy backend dependencies from deps stage (optimized)
|
||||
COPY --from=backend-deps --chown=app:app /app/node_modules ./backend/node_modules
|
||||
|
||||
# Copy backend application code (exclude unnecessary files)
|
||||
COPY --chown=app:app backend/app.js ./backend/
|
||||
COPY --chown=app:app backend/package*.json ./backend/
|
||||
COPY --chown=app:app backend/config/ ./backend/config/
|
||||
COPY --chown=app:app backend/models/ ./backend/models/
|
||||
COPY --chown=app:app backend/routes/ ./backend/routes/
|
||||
COPY --chown=app:app backend/middleware/ ./backend/middleware/
|
||||
COPY --chown=app:app backend/services/ ./backend/services/
|
||||
|
||||
# Copy minimal built frontend assets from builder stage
|
||||
COPY --from=frontend-builder --chown=app:app /app/dist ./backend/dist
|
||||
COPY --from=frontend-builder --chown=app:app /app/public/locales ./backend/dist/locales
|
||||
|
||||
# Create ultra-minimal startup script (before switching to non-root user)
|
||||
RUN printf '#!/bin/sh\nset -e\ncd backend\nmkdir -p db certs\nDB_FILE="db/production.sqlite3"\n[ "$NODE_ENV" = "development" ] && DB_FILE="db/development.sqlite3"\nif [ ! -f "$DB_FILE" ]; then\n node -e "require(\\"./models\\").sequelize.sync({force:true}).then(()=>{console.log(\\"✅ DB ready\\");process.exit(0)}).catch(e=>{console.error(\\"❌\\",e.message);process.exit(1)})"\nelse\n node -e "require(\\"./models\\").sequelize.authenticate().then(()=>{console.log(\\"✅ DB OK\\");process.exit(0)}).catch(e=>{console.error(\\"❌\\",e.message);process.exit(1)})"\nfi\nif [ -n "$TUDUDI_USER_EMAIL" ]&&[ -n "$TUDUDI_USER_PASSWORD" ]; then\n node -e "const{User}=require(\\"./models\\");const bcrypt=require(\\"bcrypt\\");(async()=>{try{const[u,c]=await User.findOrCreate({where:{email:process.env.TUDUDI_USER_EMAIL},defaults:{email:process.env.TUDUDI_USER_EMAIL,password_digest:await bcrypt.hash(process.env.TUDUDI_USER_PASSWORD,10)}});console.log(c?\\"✅ User created\\":\\"ℹ️ User exists\\");process.exit(0)}catch(e){console.error(\\"❌\\",e.message);process.exit(1)}})();"||exit 1\nfi\n[ "$TUDUDI_INTERNAL_SSL_ENABLED" = "true" ]&&[ ! -f "certs/server.crt" ]&&openssl req -x509 -newkey rsa:2048 -keyout certs/server.key -out certs/server.crt -days 365 -nodes -subj "/CN=localhost" 2>/dev/null||true\nexec node app.js\n' > start.sh && chmod +x start.sh
|
||||
|
||||
# Create necessary directories and final cleanup
|
||||
RUN mkdir -p ./backend/db ./backend/certs && \
|
||||
chown -R app:app ./backend/db ./backend/certs ./start.sh && \
|
||||
# Final size optimization - remove Node.js build tools and cache
|
||||
apk del --no-cache .runtime-deps sqlite openssl curl && \
|
||||
apk add --no-cache sqlite-libs openssl curl dumb-init && \
|
||||
rm -rf /usr/local/lib/node_modules/npm/docs /usr/local/lib/node_modules/npm/man && \
|
||||
rm -rf /root/.npm /tmp/* /var/tmp/* /var/cache/apk/*
|
||||
|
||||
# Switch to non-root user
|
||||
USER app
|
||||
|
||||
# Expose ports for both frontend (8080) and backend (9292)
|
||||
EXPOSE 8080 9292
|
||||
# Expose port
|
||||
EXPOSE 3002
|
||||
|
||||
# Set production environment variables
|
||||
ENV RACK_ENV=production \
|
||||
NODE_ENV=production \
|
||||
# Set optimized production environment variables
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3002 \
|
||||
TUDUDI_INTERNAL_SSL_ENABLED=false \
|
||||
TUDUDI_ALLOWED_ORIGINS="http://localhost:8080,http://localhost:9292,http://127.0.0.1:8080,http://127.0.0.1:9292,http://0.0.0.0:8080,http://0.0.0.0:9292" \
|
||||
LANG=C.UTF-8 \
|
||||
TZ=UTC
|
||||
TUDUDI_ALLOWED_ORIGINS="http://localhost:8080,http://localhost:3002,http://127.0.0.1:8080,http://127.0.0.1:3002" \
|
||||
TUDUDI_SESSION_SECRET="" \
|
||||
TUDUDI_USER_EMAIL="" \
|
||||
TUDUDI_USER_PASSWORD="" \
|
||||
DISABLE_TELEGRAM=false \
|
||||
DISABLE_SCHEDULER=false
|
||||
|
||||
# Generate SSL certificates if needed
|
||||
RUN mkdir -p certs && \
|
||||
if [ "$TUDUDI_INTERNAL_SSL_ENABLED" = "true" ]; then \
|
||||
openssl req -x509 -newkey rsa:4096 \
|
||||
-keyout certs/server.key -out certs/server.crt \
|
||||
-days 365 -nodes \
|
||||
-subj '/CN=localhost' \
|
||||
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"; \
|
||||
fi
|
||||
# Minimal healthcheck
|
||||
HEALTHCHECK --interval=60s --timeout=3s --start-period=10s --retries=2 \
|
||||
CMD curl -sf http://localhost:3002/api/health || exit 1
|
||||
|
||||
# Add healthcheck for backend
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:9292/api/health || exit 1
|
||||
|
||||
# Build production frontend assets
|
||||
RUN npm run build
|
||||
|
||||
# Copy translation files to dist folder for production serving
|
||||
RUN cp -r public/locales dist/
|
||||
|
||||
# Create startup script
|
||||
RUN echo '#!/bin/bash\n\
|
||||
set -e\n\
|
||||
\n\
|
||||
# Run database migrations\n\
|
||||
bundle exec rake db:migrate\n\
|
||||
\n\
|
||||
# Create user if it does not exist\n\
|
||||
if [ -n "$TUDUDI_USER_EMAIL" ] && [ -n "$TUDUDI_USER_PASSWORD" ]; then\n\
|
||||
echo "Creating user if it does not exist..."\n\
|
||||
echo "user = User.find_by(email: \"$TUDUDI_USER_EMAIL\") || User.create(email: \"$TUDUDI_USER_EMAIL\", password: \"$TUDUDI_USER_PASSWORD\"); puts \"User: #{user.email}\"" | bundle exec rake console\n\
|
||||
fi\n\
|
||||
\n\
|
||||
# Start backend with both API and static file serving\n\
|
||||
bundle exec puma -C app/config/puma.rb\n\
|
||||
' > start.sh && chmod +x start.sh
|
||||
|
||||
# Run both services
|
||||
CMD ["./start.sh"]
|
||||
# Use dumb-init for proper signal handling
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
CMD ["/app/start.sh"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue