diff --git a/.docker/config/conf.d/default.conf b/.docker/config/conf.d/default.conf new file mode 100644 index 0000000..e0c1be7 --- /dev/null +++ b/.docker/config/conf.d/default.conf @@ -0,0 +1,56 @@ +# Default server definition +server { + listen [::]:8080 default_server; + listen 8080 default_server; + server_name _; + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + + root /var/www/html; + index index.php index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to index.php + try_files $uri $uri/ /index.php?q=$uri&$args; + } + + # Redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/lib/nginx/html; + } + + # Pass the PHP scripts to PHP-FPM listening on php-fpm.sock + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + } + + # Set the cache-control headers on assets to cache for 5 days + location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ { + expires 5d; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } + + # Allow fpm ping and status from localhost + location ~ ^/(fpm-status|fpm-ping)$ { + access_log off; + allow 127.0.0.1; + deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_pass unix:/run/php-fpm.sock; + } +} diff --git a/.docker/config/fpm-pool.conf b/.docker/config/fpm-pool.conf new file mode 100644 index 0000000..4be2061 --- /dev/null +++ b/.docker/config/fpm-pool.conf @@ -0,0 +1,56 @@ +[global] +; Log to stderr +error_log = /dev/stderr + +[www] +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = /run/php-fpm.sock + +; Enable status page +pm.status_path = /fpm-status + +; Ondemand process manager +pm = ondemand + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 100 + +; The number of seconds after which an idle process will be killed. +; Note: Used only when pm is set to 'ondemand' +; Default Value: 10s +pm.process_idle_timeout = 10s; + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +; Default Value: 0 +pm.max_requests = 1000 + +; Make sure the FPM workers can reach the environment variables for configuration +clear_env = no + +; Catch output from PHP +catch_workers_output = yes + +; Remove the 'child 10 said into stderr' prefix in the log and only show the actual message +decorate_workers_output = no + +; Enable ping page to use in healthcheck +ping.path = /fpm-ping diff --git a/.docker/config/nginx.conf b/.docker/config/nginx.conf new file mode 100644 index 0000000..206334e --- /dev/null +++ b/.docker/config/nginx.conf @@ -0,0 +1,47 @@ +worker_processes auto; +error_log stderr warn; +pid /run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include mime.types; + # Threat files with a unknown filetype as binary + default_type application/octet-stream; + + # Define custom log format to include reponse times + log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$request_time $upstream_response_time $pipe $upstream_cache_status'; + + access_log /dev/stdout main_timed; + error_log /dev/stderr notice; + + keepalive_timeout 65; + + # Write temporary files to /tmp so they can be created as a non-privileged user + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # Hide headers that identify the server to prevent information leakage + proxy_hide_header X-Powered-By; + fastcgi_hide_header X-Powered-By; + server_tokens off; + + # Enable gzip compression by default + gzip on; + gzip_proxied any; + # Based on CloudFlare's recommended settings + gzip_types text/richtext text/plain text/css text/x-script text/x-component text/x-java-source text/x-markdown application/javascript application/x-javascript text/javascript text/js image/x-icon image/vnd.microsoft.icon application/x-perl application/x-httpd-cgi text/xml application/xml application/rss+xml application/vnd.api+json application/x-protobuf application/json multipart/bag multipart/mixed application/xhtml+xml font/ttf font/otf font/x-woff image/svg+xml application/vnd.ms-fontobject application/ttf application/x-ttf application/otf application/x-otf application/truetype application/opentype application/x-opentype application/font-woff application/eot application/font application/font-sfnt application/wasm application/javascript-binast application/manifest+json application/ld+json application/graphql+json application/geo+json; + gzip_vary on; + gzip_disable "msie6"; + + # Include server configs + include /etc/nginx/conf.d/*.conf; +} diff --git a/.docker/config/php.ini b/.docker/config/php.ini new file mode 100644 index 0000000..d85d12a --- /dev/null +++ b/.docker/config/php.ini @@ -0,0 +1,3 @@ +[Date] +date.timezone="UTC" +expose_php= Off \ No newline at end of file diff --git a/.docker/config/supervisord.conf b/.docker/config/supervisord.conf new file mode 100644 index 0000000..26dabbe --- /dev/null +++ b/.docker/config/supervisord.conf @@ -0,0 +1,23 @@ +[supervisord] +nodaemon=true +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/run/supervisord.pid + +[program:php-fpm] +command=php-fpm83 -F +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 + +[program:nginx] +command=nginx -g 'daemon off;' +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 diff --git a/Dockerfile b/Dockerfile index 74b1335..d15b7a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,163 +1,64 @@ -# Accepted values: 8.3 - 8.2 -ARG PHP_VERSION=8.3 - -ARG COMPOSER_VERSION=latest - -########################################### -# Build frontend assets with NPM -########################################### - -ARG NODE_VERSION=20-alpine - -FROM node:${NODE_VERSION} AS build - -ENV ROOT=/var/www/html - -WORKDIR ${ROOT} - -RUN npm config set update-notifier false && npm set progress=false - -COPY package*.json ./ - -RUN if [ -f $ROOT/package-lock.json ]; \ - then \ - npm ci --loglevel=error --no-audit; \ - else \ - npm install --loglevel=error --no-audit; \ - fi - -COPY . . - -RUN npm run build - -########################################### - -FROM composer:${COMPOSER_VERSION} AS vendor - -FROM php:${PHP_VERSION}-cli-bookworm AS base - -LABEL maintainer="Curtis Delicat /etc/timezone - -ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ - -RUN apt-get update; \ - apt-get upgrade -yqq; \ - apt-get install -yqq --no-install-recommends --show-progress \ - apt-utils \ +ARG ALPINE_VERSION=3.19 +FROM alpine:${ALPINE_VERSION} +LABEL Maintainer="Tim de Pater " +LABEL Description="Lightweight container with Nginx 1.24 & PHP 8.3 based on Alpine Linux." +# Setup document root +WORKDIR /var/www/html + +# Install packages and remove default server definition +RUN apk add --no-cache \ curl \ - wget \ - nano \ - ncdu \ - ca-certificates \ - supervisor \ - libsodium-dev \ - # Install PHP extensions - && install-php-extensions \ - bz2 \ - pcntl \ - mbstring \ - bcmath \ - sockets \ - pgsql \ - pdo_pgsql \ - opcache \ - exif \ - pdo_mysql \ - zip \ - intl \ - gd \ - redis \ - rdkafka \ - memcached \ - igbinary \ - ldap \ - swoole \ - && apt-get -y autoremove \ - && apt-get clean \ - && docker-php-source delete \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ - && rm /var/log/lastlog /var/log/faillog - -RUN wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" \ - -O /usr/bin/supercronic \ - && chmod +x /usr/bin/supercronic \ - && mkdir -p /etc/supercronic \ - && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --verbose --no-interaction" > /etc/supercronic/laravel - -RUN userdel --remove --force www-data \ - && groupadd --force -g ${WWWGROUP} ${USER} \ - && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} - -RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ - && chmod -R a+rw /var/{log,run} - -RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini - -USER ${USER} - -COPY --chown=${USER}:${USER} --from=vendor /usr/bin/composer /usr/bin/composer -COPY --chown=${USER}:${USER} composer.json composer.lock ./ - -RUN composer install \ - --no-dev \ - --no-interaction \ - --no-autoloader \ - --no-ansi \ - --no-scripts \ - --audit - -COPY --chown=${USER}:${USER} . . -COPY --chown=${USER}:${USER} --from=build ${ROOT}/public public - -RUN mkdir -p \ - storage/framework/{sessions,views,cache,testing} \ - storage/logs \ - bootstrap/cache && chmod -R a+rw storage - -COPY --chown=${USER}:${USER} .docker/octane/Swoole/supervisord.swoole.conf /etc/supervisor/conf.d/ -COPY --chown=${USER}:${USER} .docker/supervisord.*.conf /etc/supervisor/conf.d/ -COPY --chown=${USER}:${USER} .docker/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini -COPY --chown=${USER}:${USER} .docker/start-container /usr/local/bin/start-container - -RUN composer install \ - --classmap-authoritative \ - --no-interaction \ - --no-ansi \ - --no-dev \ - && composer clear-cache \ - && php artisan storage:link - -RUN chmod +x /usr/local/bin/start-container - -RUN cat .docker/utilities.sh >> ~/.bashrc - + nginx \ + php83 \ + php83-ctype \ + php83-curl \ + php83-dom \ + php83-fileinfo \ + php83-fpm \ + php83-gd \ + php83-intl \ + php83-mbstring \ + php83-mysqli \ + php83-opcache \ + php83-openssl \ + php83-phar \ + php83-session \ + php83-tokenizer \ + php83-xml \ + php83-xmlreader \ + php83-xmlwriter \ + supervisor + +# Configure nginx - http +COPY .docker/config/nginx.conf /etc/nginx/nginx.conf +# Configure nginx - default server +COPY .docker/config/conf.d /etc/nginx/conf.d/ + +# Configure PHP-FPM +ENV PHP_INI_DIR /etc/php83 +COPY .docker/config/fpm-pool.conf ${PHP_INI_DIR}/php-fpm.d/www.conf +COPY .docker/config/php.ini ${PHP_INI_DIR}/conf.d/custom.ini + +# Configure supervisord +COPY .docker/config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# Make sure files/folders needed by the processes are accessable when they run under the nobody user +RUN chown -R nobody.nobody /var/www/html /run /var/lib/nginx /var/log/nginx + +# Create symlink for php +RUN ln -s /usr/bin/php83 /usr/bin/php + +# Switch to use a non-root user from here on +USER nobody + +# Add application +COPY --chown=nobody src/ /var/www/html/ + +# Expose the port nginx is reachable on EXPOSE 80 -ENTRYPOINT ["start-container"] +# Let supervisord start nginx & php-fpm +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] -HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD php artisan octane:status || exit 1 +# Configure a healthcheck to validate that everything is up&running +HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1:8080/fpm-ping || exit 1