Skip to content

Instantly share code, notes, and snippets.

@BretFisher
Last active November 2, 2023 12:31
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save BretFisher/54ff7c4cae294c39f1afea4786efd321 to your computer and use it in GitHub Desktop.
Save BretFisher/54ff7c4cae294c39f1afea4786efd321 to your computer and use it in GitHub Desktop.
WIP sample Laravel php_fpm plus nginx plus supervisor Docker setup with npm, composer, bower, and more
FROM php:7.0-fpm
# this is a sample BASE image, that php_fpm projects can start FROM
# it's got a lot in it, but it's designed to meet dev and prod needs in single image
# I've tried other things like splitting out php_fpm and nginx containers
# or multi-stage builds to keep it lean, but this is my current design for
## single image that does nginx and php_fpm
## usable with bind-mount and unique dev-only entrypoint file that builds
## some things on startup when developing locally
## stores all code in image with proper default builds for production
# install apt dependencies
# some of these are not needed in all php projects
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
ca-certificates \
curl \
dos2unix \
git \
g++ \
jq \
libedit-dev \
libfcgi0ldbl \
libfreetype6-dev \
libicu-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng12-dev \
libpq-dev \
libssl-dev \
mcrypt \
openssh-client \
supervisor \
unzip \
zip \
&& rm -r /var/lib/apt/lists/*
#gnupg1 \
# help docker-php-ext-install find ldap libs
RUN ln -s /usr/lib/x86_64-linux-gnu/libldap.so /usr/lib/libldap.so \
&& ln -s /usr/lib/x86_64-linux-gnu/liblber.so /usr/lib/liblber.so
# Install extensions using the helper script provided by the base image
RUN docker-php-ext-install \
mcrypt \
pdo_mysql \
mysqli \
json \
readline \
gd \
intl
# configure gd
RUN docker-php-ext-configure gd \
--enable-gd-native-ttf \
--with-freetype-dir=/usr/include/freetype2 \
--with-jpeg-dir=/usr/include/
# configure intl
RUN docker-php-ext-configure intl
# install nginx (copied from official nginx Dockerfile)
ENV NGINX_VERSION 1.12.1-1~jessie
ENV NJS_VERSION 1.12.1.0.1.10-1~jessie
RUN NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
found=''; \
for server in \
ha.pool.sks-keyservers.net \
hkp://keyserver.ubuntu.com:80 \
hkp://p80.pool.sks-keyservers.net:80 \
pgp.mit.edu \
; do \
echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
done; \
test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
echo "deb http://nginx.org/packages/debian/ jessie nginx" >> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
nginx=${NGINX_VERSION} \
nginx-module-xslt=${NGINX_VERSION} \
nginx-module-geoip=${NGINX_VERSION} \
nginx-module-image-filter=${NGINX_VERSION} \
nginx-module-njs=${NJS_VERSION} \
gettext-base \
&& rm -rf /var/lib/apt/lists/*
# forward nginx request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
# install composer so we can run dump-autoload at entrypoint startup in dev
# copied from official composer Dockerfile
ENV PATH="/composer/vendor/bin:$PATH" \
COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_VENDOR_DIR=/var/www/vendor \
COMPOSER_HOME=/composer \
COMPOSER_VERSION=1.4.3
RUN curl -s -f -L -o /tmp/installer.php https://raw.githubusercontent.com/composer/getcomposer.org/da290238de6d63faace0343efbdd5aa9354332c5/web/installer \
&& php -r " \
\$signature = '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410'; \
\$hash = hash('SHA384', file_get_contents('/tmp/installer.php')); \
if (!hash_equals(\$signature, \$hash)) { \
unlink('/tmp/installer.php'); \
echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \
exit(1); \
}" \
&& php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} \
&& rm /tmp/installer.php \
&& composer --ansi --version --no-interaction
# install node for running gulp at container entrypoint startup in dev
# copied from official node Dockerfile
# gpg keys listed at https://github.com/nodejs/node#release-team
RUN set -ex \
&& for key in \
9554F04D7259F04124DE6B476D5A82AC7E37093B \
94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
FD3A5288F042B6850C66B31F09FE44734EB7990E \
71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
B9AE9905FFD7803F25714661B63B535A4C206CA9 \
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
56730D5401028683275BD23C23EFEFE93C4CFFFE \
; do \
gpg --keyserver pgp.mit.edu --recv-keys "$key" || \
gpg --keyserver keyserver.pgp.com --recv-keys "$key" || \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key" ; \
done
ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 6.11.2
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
ENV PATH /var/www/node_modules/.bin:$PATH
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
version: '3.3'
services:
php:
# note you need to manually build this image from base dockerfile above FIRST!
image: yourdockername/base-php-nginx
build:
context: .
args:
BUILDKEY: |
-----BEGIN RSA PRIVATE KEY-----
lakdjfladjlkjadflkjadflajdfkja
-----END RSA PRIVATE KEY-----
volumes:
- .:/var/www/app:delegated
- ./supervisord.conf:/etc/supervisor/conf.d/supervisord.conf
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/nginx-site.conf:/etc/nginx/conf.d/default.conf
- ./nginx/dev-cert.crt:/etc/nginx/ssl/cert.crt
- ./nginx/dev-key.pem:/etc/nginx/ssl/key.pem
entrypoint: /usr/local/bin/docker-php-entrypoint-dev
command: ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
ports:
- "${PI_PORT_HTTP:-8080}:80"
- "${PI_PORT_HTTPS:-8443}:443"
- "${PORT_XDEBUG:-9001}:9001"
depends_on:
- mysql
environment:
APP_NAME: Laravel
APP_ENV: local
APP_DEBUG: true
APP_KEY: KEYGOESHERE
APP_LOG: errorlog
APP_URL: "http://localhost"
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: homestead
DB_USERNAME: homestead
DB_PASSWORD: secret
mysql:
image: mysql:5.7
volumes:
- mysql:/var/lib/mysql
ports:
- "${PORT_MYSQL:-3306}:3306"
environment:
MYSQL_DATABASE: ${DB_DATABASE:-homestead}
MYSQL_USER: ${DB_USERNAME:-homestead}
MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-secret}
# using this named volume ensures db's hang around between "up's"
volumes:
mysql:
# secrets:
#!/bin/sh
set -e
# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
set -- php-fpm "$@"
fi
# write the .env config
{ \
echo APP_NAME="$APP_NAME"; \
echo APP_ENV="$APP_ENV"; \
echo APP_DEBUG="$APP_DEBUG"; \
echo APP_KEY="$APP_KEY"; \
echo APP_LOG="$APP_LOG"; \
echo APP_URL="$APP_URL"; \
echo DB_CONNECTION="$DB_CONNECTION"; \
echo DB_HOST="$DB_HOST"; \
echo DB_PORT="$DB_PORT"; \
echo DB_DATABASE="$DB_DATABASE"; \
echo DB_USERNAME="$DB_USERNAME"; \
echo DB_PASSWORD="$DB_PASSWORD";
} > /var/www/app/.env
# write the php-fpm config
{ \
echo listen = /var/run/php-fpm.sock; \
echo listen.owner = www-data; \
echo listen.group = www-data; \
echo ping.path = /ping; \
echo pm.status_path = /status; \
echo pm.max_children = "$FPM_PM_MAX_CHILDREN"; \
echo pm.start_servers = "$FPM_PM_START_SERVERS"; \
echo pm.min_spare_servers = "$FPM_PM_MIN_SPARE_SERVERS"; \
echo pm.max_spare_servers = "$FPM_PM_MAX_SPARE_SERVERS"; \
} > /usr/local/etc/php-fpm.d/zzz-app.conf
exec "$@"
#!/bin/sh
set -e
# run last minute build tools just for local dev
# this file should just be used to override on local dev in a compose file
# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
set -- php-fpm "$@"
fi
# run default entrypoint
/usr/local/bin/docker-php-entrypoint
# run last minute build tools just for local dev
cd /var/www/app
composer dump-autoload
cd /var/www/app/public
exec "$@"
FROM yourdockername/base-php-nginx:latest AS build
# BUILD STAGE
# the primary reason we have two build stages is so SSH key of private repo's will never
# be in final image
# COPY IN BUILD SSH KEY
# It won't be copied to final image
# add this build arg to compose file
ARG BUILDKEY
RUN if [ -z "$BUILDKEY" ]; then echo "BUILDKEY SSH NOT SET - ERROR"; exit 1; else : ; fi
WORKDIR /root/.ssh
RUN echo "$BUILDKEY" > id_rsa && chmod 600 id_rsa
# add bitbucket and github to known hosts for ssh needs
RUN ssh-keyscan -t rsa bitbucket.org >> /root/.ssh/known_hosts \
&& ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts
##
## compose build tools
##
# install composer dependencies
WORKDIR /var/www/app
COPY ./composer.json ./composer.lock ./
ENV COMPOSER_VENDOR_DIR=/var/www/vendor
# RUN composer config github-oauth.github.com YOUROAUTHKEYHERE
RUN composer install --no-scripts --no-autoloader --ansi --no-interaction
##
## Node Build Tools
##
# we hardcode to develop so all tools are there for npm build
ENV NODE_ENV=develop
# install dependencies first, in a different location for easier app bind mounting for local development
WORKDIR /var/www
COPY ./package.json .
RUN npm install
# no need to cache clean in non-final build steps
ENV PATH /var/www/node_modules/.bin:$PATH
ENV NODE_PATH=/var/www/node_modules
WORKDIR /var/www/app
##
## Go for Bower
##
COPY ./bower.json .
RUN bower install --allow-root
################
FROM yourdockername/base-php-nginx:latest AS php
# FINAL STAGE
WORKDIR /var/www
COPY --from=build /var/www .
# ensure node can reach our modules
ENV PATH /var/www/node_modules/.bin:$PATH
ENV NODE_PATH=/var/www/node_modules
# ensure compose can fine its vendor
ENV COMPOSER_VENDOR_DIR=/var/www/vendor
# add custom php-fpm pool settings, these get written at entrypoint startup
ENV FPM_PM_MAX_CHILDREN=20 \
FPM_PM_START_SERVERS=2 \
FPM_PM_MIN_SPARE_SERVERS=1 \
FPM_PM_MAX_SPARE_SERVERS=3
# Laravel App Config
# setup app config environment at runtime
# gets put into ./.env at startup
ENV APP_NAME=Laravel \
APP_ENV=local \
APP_DEBUG=true \
APP_KEY=KEYGOESHERE \
APP_LOG=errorlog \
APP_URL=http://localhost \
DB_CONNECTION=mysql \
DB_HOST=mysql \
DB_PORT=3306 \
DB_DATABASE=homestead \
DB_USERNAME=homestead \
DB_PASSWORD=secret
# Many more ENV may be needed here, and updated in docker-php-entrypoint file
# update the entrypoint to write config files and do last minute builds on startup
# notice we have a -dev version, which does different things on local docker-compose
# but we'll default to entrypoint of running the non -dev one
COPY docker-php-* /usr/local/bin/
RUN dos2unix /usr/local/bin/docker-php-entrypoint
RUN dos2unix /usr/local/bin/docker-php-entrypoint-dev
# copy in nginx config
# NOTE, we're copying in default dev self-signed certs
# for prod, use Swarm secrets and overwrite these files with proper cert
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./nginx-site.conf /etc/nginx/conf.d/default.conf
COPY ./dev-cert.crt /etc/nginx/ssl/cert.crt
COPY ./dev-key.pem /etc/nginx/ssl/key.pem
# copy in app code as late as possible, as it changes the most
WORKDIR /var/www/app
COPY . .
RUN chown -R www-data:www-data /var/www/app/cache
RUN chown -R www-data:www-data /var/www/app/storage
RUN composer dump-autoload -o
# NOTE: need a valid health URL to make this work
# this is one for php_fpm but we can do a nginx curl much easier
# HEALTHCHECK --interval=10s --timeout=3s \
# CMD \
# SCRIPT_NAME=/ping \
# SCRIPT_FILENAME=/ping \
# REQUEST_METHOD=GET \
# cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1
WORKDIR /var/www/app/public
EXPOSE 80 443 9000 9001
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
server {
listen 80;
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/key.pem;
root /var/www/app/public;
index index.php index.html index.htm;
server_name nginx;
#App paths
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
}
# nginx status page
location /status-nginx {
stub_status on;
access_log off;
}
# fpm status page and ping page
location ~ ^/(status|ping)$ {
access_log off;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
user www-data;
worker_processes auto;
worker_rlimit_nofile 8192;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid
loglevel = INFO
[program:php-fpm]
command = /usr/local/sbin/php-fpm
autostart=true
autorestart=true
priority=5
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
priority=10
stdout_events_enabled=true
stderr_events_enabled=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment