From f6c845e627036bb1cfdc9ebe5f45cc13e56a5093 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 15 Mar 2026 14:11:32 +0000 Subject: [PATCH] Add minimal secure Dockerfile for Coolify deployment --- Dockerfile | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..c39e22e3ef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,173 @@ +############################################################################### +# EspoCRM - Minimal Secure Build from Source +# Multi-stage build with Alpine base images +############################################################################### + +# Stage 1: Build frontend assets +FROM node:20-alpine AS frontend +WORKDIR /build +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts +COPY . . +RUN npx grunt + +# Stage 2: Install PHP dependencies +FROM composer:2 AS composer +WORKDIR /build +COPY composer.json composer.lock ./ +RUN composer install \ + --no-dev \ + --no-interaction \ + --no-progress \ + --prefer-dist \ + --optimize-autoloader \ + --ignore-platform-reqs + +# Stage 3: Minimal runtime +FROM php:8.4-fpm-alpine + +RUN set -ex; \ + apk add --no-cache \ + nginx \ + freetype \ + libjpeg-turbo \ + libwebp \ + libpng \ + libzip \ + libpq \ + libldap \ + icu-libs \ + unzip \ + ; \ + apk add --no-cache --virtual .build-deps \ + freetype-dev \ + libjpeg-turbo-dev \ + libwebp-dev \ + libpng-dev \ + libzip-dev \ + postgresql-dev \ + openldap-dev \ + icu-dev \ + ; \ + docker-php-ext-configure gd \ + --with-freetype --with-jpeg --with-webp \ + ; \ + docker-php-ext-install -j$(nproc) \ + gd pdo_mysql pdo_pgsql zip ldap \ + exif pcntl posix bcmath intl \ + ; \ + apk del .build-deps; \ + rm -rf /tmp/* /var/cache/apk/* + +# PHP hardened config +RUN { \ + echo 'expose_php = Off'; \ + echo 'display_errors = Off'; \ + echo 'display_startup_errors = Off'; \ + echo 'log_errors = On'; \ + echo 'memory_limit = 256M'; \ + echo 'max_execution_time = 180'; \ + echo 'max_input_time = 180'; \ + echo 'post_max_size = 50M'; \ + echo 'upload_max_filesize = 50M'; \ + echo 'session.cookie_httponly = 1'; \ + echo 'session.cookie_secure = 1'; \ + echo 'session.use_strict_mode = 1'; \ +} > "$PHP_INI_DIR/conf.d/espocrm.ini" + +# Nginx config +RUN cat > /etc/nginx/http.d/default.conf <<'NGINX' +server { + listen 80; + server_name _; + root /var/www/html; + index index.php index.html; + + server_tokens off; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "same-origin" always; + + client_max_body_size 50M; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_read_timeout 180; + } + + location ~ /\. { deny all; } + location ~ ^/(data|application|custom|vendor)/ { deny all; } + location = /data/config.php { deny all; } +} +NGINX + +WORKDIR /var/www/html + +# Copy application +COPY --chown=82:82 . . +COPY --chown=82:82 --from=composer /build/vendor ./vendor +COPY --chown=82:82 --from=frontend /build/client ./client + +# Remove dev/build files +RUN rm -rf node_modules .git .github tests dev \ + Gruntfile.js package.json package-lock.json \ + phpstan.neon phpunit.xml .idea .vscode \ + .editorconfig .gitattributes .gitignore .npmrc \ + diff.js lang.js po.js jsconfig.json + +# Set permissions +RUN set -ex; \ + find . -type d -exec chmod 755 {} +; \ + find . -type f -exec chmod 644 {} +; \ + chmod 754 bin/command 2>/dev/null || true; \ + for dir in data custom client/custom; do \ + mkdir -p "$dir"; \ + chown -R 82:82 "$dir"; \ + done + +# Entrypoint +COPY --chmod=755 <<'EOF' /entrypoint.sh +#!/bin/sh +set -e + +if [ -n "$ESPOCRM_DATABASE_HOST" ] && [ ! -f "data/config.php" ]; then + echo "Waiting for database..." + i=0 + while [ $i -lt 30 ]; do + if php -r "new PDO('mysql:host=${ESPOCRM_DATABASE_HOST};port=${ESPOCRM_DATABASE_PORT:-3306}','${ESPOCRM_DATABASE_USER:-root}','${ESPOCRM_DATABASE_PASSWORD}');" 2>/dev/null; then + echo "Database ready. Running installer..." + php bin/command install \ + --databasePlatform="${ESPOCRM_DATABASE_PLATFORM:-Mysql}" \ + --databaseHost="$ESPOCRM_DATABASE_HOST" \ + --databasePort="${ESPOCRM_DATABASE_PORT:-3306}" \ + --databaseName="${ESPOCRM_DATABASE_NAME:-espocrm}" \ + --databaseUser="${ESPOCRM_DATABASE_USER:-root}" \ + --databasePassword="$ESPOCRM_DATABASE_PASSWORD" \ + --adminUsername="${ESPOCRM_ADMIN_USERNAME:-admin}" \ + --adminPassword="${ESPOCRM_ADMIN_PASSWORD:-admin}" \ + --siteUrl="${ESPOCRM_SITE_URL:-http://localhost}" \ + && echo "Install complete." \ + || echo "CLI install failed. Use web installer." + break + fi + i=$((i + 1)) + sleep 2 + done +fi + +php-fpm -D +exec nginx -g 'daemon off;' +EOF + +VOLUME /var/www/html/data +EXPOSE 80 + +ENTRYPOINT ["/entrypoint.sh"]