tududi/Dockerfile
Chris 03f38f05dc
Setup intelligence (#84)
* Add next suggestions and remove console logs

* Add pomodoro timer

* Add pomodoro switch in settings

* Fix pomodoro setting

* Add timezones to settings

* Fix an issue with password reset

* Cleanup

* Sort tags alphabetically

* Clean up today's view

* Add an indicator for repeatedly added to today

* Refactor tags

* Add due date today item

* Move recurrence to the subtitle area

* Fix today layout

* Add a badge to Inbox items

* Move inbox badge to sidebar

* Add quotes and progress bar

* Add translations for quotes

* Fix test issues

* Add helper script for docker local

* Set up overdue tasks

* Add  linux/arm/v7 build to deploy script

* Add  linux/arm/v7 build to deploy script pt2

* Fix an issue with helmet and SSL

* Add volume db persistence

* Fix cog icon issues
2025-06-27 14:02:18 +03:00

170 lines
No EOL
7.7 KiB
Docker
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================================
# Ultra-optimized multi-stage build for minimal rootless Docker image
# ============================================================================
# Stage 1: Frontend Build Environment (optimized)
FROM node:20-alpine AS frontend-builder
WORKDIR /app
# Copy frontend package files
COPY package*.json ./
COPY webpack.config.js babel.config.js tsconfig.json postcss.config.js tailwind.config.js ./
# 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 frontend source code
COPY frontend/ frontend/
COPY public/ public/
# 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 ! -path "*/googleapis/*" -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 && \
# Retry npm install with exponential backoff for network issues
for i in 1 2 3; do \
npm install --no-audit --no-fund && break || \
(echo "npm install failed, attempt $i/3, retrying in $((i*5)) seconds..." && sleep $((i*5))); \
done
# Copy backend source code including tests
COPY backend/ ./backend/
# Run tests
RUN cd backend && npm test
# Create test completion marker to ensure tests passed
RUN echo "Tests passed successfully" > /app/test-success.marker
# Stage 4: Final Production Image (minimal base)
FROM node:20-alpine AS production
# Copy test success marker to ensure tests passed before production build
COPY --from=test /app/test-success.marker /tmp/test-success.marker
# 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\n# Check and create directories with proper permissions\nif [ ! -d "db" ]; then\n mkdir -p db\nfi\nif [ ! -w "db" ]; then\n echo "❌ ERROR: Database directory /app/backend/db is not writable by user app (1001:1001)"\n echo " If using Docker volumes, ensure the host directory has proper ownership:"\n echo " sudo chown -R 1001:1001 /host/path/to/db"\n echo " Or use an anonymous volume: docker run -v /app/backend/db ..."\n exit 1\nfi\nmkdir -p 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/*
# Declare volume for database persistence
VOLUME ["/app/backend/db"]
# Switch to non-root user
USER app
# Expose port
EXPOSE 3002
# Set optimized production environment variables
ENV NODE_ENV=production \
PORT=3002 \
TUDUDI_INTERNAL_SSL_ENABLED=false \
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
# Minimal healthcheck
HEALTHCHECK --interval=60s --timeout=3s --start-period=10s --retries=2 \
CMD curl -sf http://localhost:3002/api/health || exit 1
# Use dumb-init for proper signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["/app/start.sh"]