Add minimal secure Dockerfile for Coolify deployment
This commit is contained in:
+173
@@ -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"]
|
||||
Reference in New Issue
Block a user