-
-
Save chris-cmsoft/d398cb87907db6f39341df53345d6925 to your computer and use it in GitHub Desktop.
/vendor | |
/node_modules |
version: '3' | |
services: | |
# We need to run the FPM container for our application | |
laravel.fpm: | |
build: | |
context: . | |
target: fpm_server | |
image: laravel-in-kubernetes/fpm_server | |
# We can override any env values here. | |
# By default the .env in the project root will be loaded as the environment for all containers | |
environment: | |
APP_DEBUG: "true" | |
# Mount the codebase, so any code changes we make will be propagated to the running application | |
volumes: | |
# Here we mount in our codebase so any changes are immediately reflected into the container | |
- '.:/opt/apps/laravel-in-kubernetes' | |
networks: | |
- laravel-in-kubernetes | |
# Run the web server container for static content, and proxying to our FPM container | |
laravel.web: | |
build: | |
context: . | |
target: web_server | |
image: laravel-in-kubernetes/web_server | |
# Expose our application port (80) through a port on our local machine (8080) | |
ports: | |
- '8080:80' | |
environment: | |
# We need to pass in the new FPM hst as the name of the fpm container on port 9000 | |
FPM_HOST: "laravel.fpm:9000" | |
# Mount the public directory into the container so we can serve any static files directly when they change | |
volumes: | |
# Here we mount in our codebase so any changes are immediately reflected into the container | |
- './public:/opt/apps/laravel-in-kubernetes/public' | |
networks: | |
- laravel-in-kubernetes | |
# Run the Laravel Scheduler | |
laravel.cron: | |
build: | |
context: . | |
target: cron | |
image: laravel-in-kubernetes/cron | |
# Here we mount in our codebase so any changes are immediately reflected into the container | |
volumes: | |
# Here we mount in our codebase so any changes are immediately reflected into the container | |
- '.:/opt/apps/laravel-in-kubernetes' | |
networks: | |
- laravel-in-kubernetes | |
# Run the frontend, and file watcher in a container, so any changes are immediately compiled and servable | |
laravel.frontend: | |
build: | |
context: . | |
target: frontend | |
# Override the default CMD, so we can watch changes to frontend files, and re-transpile them. | |
command: ["npm", "run", "watch"] | |
image: laravel-in-kubernetes/frontend | |
volumes: | |
# Here we mount in our codebase so any changes are immediately reflected into the container | |
- '.:/opt/apps/laravel-in-kubernetes' | |
# Add node_modeules as singular volume. | |
# This prevents our local node_modules from being propagated into the container, | |
# So the node_modules can be compiled for each of the different architectures (Local, Image) | |
- '/opt/app/node_modules/' | |
networks: | |
- laravel-in-kubernetes | |
mysql: | |
image: 'mysql:8.0' | |
ports: | |
- '${FORWARD_DB_PORT:-3306}:3306' | |
environment: | |
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' | |
MYSQL_DATABASE: '${DB_DATABASE}' | |
MYSQL_USER: '${DB_USERNAME}' | |
MYSQL_PASSWORD: '${DB_PASSWORD}' | |
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' | |
volumes: | |
- 'laravel-in-kubernetes-mysql:/var/lib/mysql' | |
networks: | |
- laravel-in-kubernetes | |
healthcheck: | |
test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"] | |
retries: 3 | |
timeout: 5s | |
networks: | |
laravel-in-kubernetes: | |
volumes: | |
laravel-in-kubernetes-mysql: |
# Create args for PHP extensions and PECL packages we need to install. | |
# This makes it easier if we want to install packages, | |
# as we have to install them in multiple places. | |
# This helps keep ou Dockerfiles DRY -> https://bit.ly/dry-code | |
# You can see a list of required extensions for Laravel here: https://laravel.com/docs/8.x/deployment#server-requirements | |
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql tokenizer dom pcntl" | |
ARG PHP_PECL_EXTS="redis" | |
# We need to build the Composer base to reuse packages we've installed | |
FROM composer:2.1 as composer_base | |
# We need to declare that we want to use the args in this build step | |
ARG PHP_EXTS | |
ARG PHP_PECL_EXTS | |
# First, create the application directory, and some auxilary directories for scripts and such | |
RUN mkdir -p /opt/apps/laravel-in-kubernetes /opt/apps/laravel-in-kubernetes/bin | |
# Next, set our working directory | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
# We need to create a composer group and user, and create a home directory for it, so we keep the rest of our image safe, | |
# And not accidentally run malicious scripts | |
RUN addgroup -S composer \ | |
&& adduser -S composer -G composer \ | |
&& chown -R composer /opt/apps/laravel-in-kubernetes \ | |
&& apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev \ | |
&& docker-php-ext-install -j$(nproc) ${PHP_EXTS} \ | |
&& pecl install ${PHP_PECL_EXTS} \ | |
&& docker-php-ext-enable ${PHP_PECL_EXTS} \ | |
&& apk del build-dependencies | |
# Next we want to switch over to the composer user before running installs. | |
# This is very important, so any extra scripts that composer wants to run, | |
# don't have access to the root filesystem. | |
# This especially important when installing packages from unverified sources. | |
USER composer | |
# Copy in our dependency files. | |
# We want to leave the rest of the code base out for now, | |
# so Docker can build a cache of this layer, | |
# and only rebuild when the dependencies of our application changes. | |
COPY --chown=composer composer.json composer.lock ./ | |
# Install all the dependencies without running any installation scripts. | |
# We skip scripts as the code base hasn't been copied in yet and script will likely fail, | |
# as `php artisan` available yet. | |
# This also helps us to cache previous runs and layers. | |
# As long as comoser.json and composer.lock doesn't change the install will be cached. | |
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist | |
# Copy in our actual source code so we can run the installation scripts we need | |
# At this point all the PHP packages have been installed, | |
# and all that is left to do, is to run any installation scripts which depends on the code base | |
COPY --chown=composer . . | |
# Now that the code base and packages are all available, | |
# we can run the install again, and let it run any install scripts. | |
RUN composer install --no-dev --prefer-dist | |
# For the frontend, we want to get all the Laravel files, | |
# and run a production compile | |
FROM node:14 as frontend | |
# We need to copy in the Laravel files to make everything is available to our frontend compilation | |
COPY --from=composer_base /opt/apps/laravel-in-kubernetes /opt/apps/laravel-in-kubernetes | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
# We want to install all the NPM packages, | |
# and compile the MIX bundle for production | |
RUN npm install && \ | |
npm run prod | |
# For running things like migrations, and queue jobs, | |
# we need a CLI container. | |
# It contains all the Composer packages, | |
# and just the basic CLI "stuff" in order for us to run commands, | |
# be that queues, migrations, tinker etc. | |
FROM php:8.0-alpine as cli | |
# We need to declare that we want to use the args in this build step | |
ARG PHP_EXTS | |
ARG PHP_PECL_EXTS | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
# We need to install some requirements into our image, | |
# used to compile our PHP extensions, as well as install all the extensions themselves. | |
# You can see a list of required extensions for Laravel here: https://laravel.com/docs/8.x/deployment#server-requirements | |
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \ | |
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \ | |
pecl install ${PHP_PECL_EXTS} && \ | |
docker-php-ext-enable ${PHP_PECL_EXTS} && \ | |
apk del build-dependencies | |
# Next we have to copy in our code base from our initial build which we installed in the previous stage | |
COPY --from=composer_base /opt/apps/laravel-in-kubernetes /opt/apps/laravel-in-kubernetes | |
COPY --from=frontend /opt/apps/laravel-in-kubernetes/public /opt/apps/laravel-in-kubernetes/public | |
# We need a stage which contains FPM to actually run and process requests to our PHP application. | |
FROM php:8.0-fpm-alpine as fpm_server | |
# We need to declare that we want to use the args in this build step | |
ARG PHP_EXTS | |
ARG PHP_PECL_EXTS | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \ | |
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \ | |
pecl install ${PHP_PECL_EXTS} && \ | |
docker-php-ext-enable ${PHP_PECL_EXTS} && \ | |
apk del build-dependencies | |
# We have to copy in our code base from our initial build which we installed in the previous stage | |
COPY --from=composer_base --chown=www-data /opt/apps/laravel-in-kubernetes /opt/apps/laravel-in-kubernetes | |
COPY --from=frontend --chown=www-data /opt/apps/laravel-in-kubernetes/public /opt/apps/laravel-in-kubernetes/public | |
# We want to cache the event, routes, and views so we don't try to write them when we are in Kubernetes. | |
# Docker builds should be as immutable as possible, and this removes a lot of the writing of the live application. | |
RUN php artisan event:cache && \ | |
php artisan route:cache && \ | |
php artisan view:cache | |
# As FPM uses the www-data user when running our application, | |
# we need to make sure that we also use that user when starting up, | |
# so our user "owns" the application when running | |
USER www-data | |
# We need an nginx container which can pass requests to our FPM container, | |
# as well as serve any static content. | |
FROM nginx:1.20-alpine as web_server | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
# We need to add our NGINX template to the container for startup, | |
# and configuration. | |
COPY nginx.conf.template /etc/nginx/templates/default.conf.template | |
# Copy in ONLY the public directory of our project. | |
# This is where all the static assets will live, which nginx will serve for us. | |
COPY --from=frontend /opt/apps/laravel-in-kubernetes/public /opt/apps/laravel-in-kubernetes/public | |
# We need a CRON container to the Laravel Scheduler. | |
# We'll start with the CLI container as our base, | |
# as we only need to override the CMD which the container starts with to point at cron | |
FROM cli as cron | |
WORKDIR /opt/apps/laravel-in-kubernetes | |
# We want to create a laravel.cron file with Laravel cron settings, which we can import into crontab, | |
# and run crond as the primary command in the forground | |
RUN touch laravel.cron && \ | |
echo "* * * * * cd /opt/apps/laravel-in-kubernetes && php artisan schedule:run" >> laravel.cron && \ | |
crontab laravel.cron | |
CMD ["crond", "-l", "2", "-f"] | |
FROM cli |
server { | |
listen 80 default_server; | |
listen [::]:80 default_server; | |
# We need to set the root for our sevrer, | |
# so any static file requests gets loaded from the correct path | |
root /opt/apps/laravel-in-kubernetes/public; | |
index index.php index.html index.htm index.nginx-debian.html; | |
# _ makes sure that nginx does not try to map requests to a specific hostname | |
# This allows us to specify the urls to our application as infrastructure changes, | |
# without needing to change the application | |
server_name _; | |
# At the root location, | |
# we first check if there are any static files at the location, and serve those, | |
# If not, we check whether there is an indexable folder which can be served, | |
# Otherwise we forward the request to the PHP server | |
location / { | |
# Using try_files here is quite important as a security concideration | |
# to prevent injecting PHP code as static assets, | |
# and then executing them via a URL. | |
# See https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#passing-uncontrolled-requests-to-php | |
try_files $uri $uri/ /index.php?$query_string; | |
} | |
# Some static assets are loaded on every page load, | |
# and logging these turns into a lot of useless logs. | |
# If you would prefer to see these requests for catching 404's etc. | |
# Feel free to remove them | |
location = /favicon.ico { access_log off; log_not_found off; } | |
location = /robots.txt { access_log off; log_not_found off; } | |
# When a 404 is returned, we want to display our applications 404 page, | |
# so we redirect it to index.php to load the correct page | |
error_page 404 /index.php; | |
# Whenever we receive a PHP url, or our root location block gets to serving through fpm, | |
# we want to pass the request to FPM for processing | |
location ~ \.php$ { | |
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini | |
include fastcgi_params; | |
fastcgi_intercept_errors on; | |
fastcgi_pass ${FPM_HOST}; | |
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; | |
} | |
location ~ /\.ht { | |
deny all; | |
} | |
location ~ /\.(?!well-known).* { | |
deny all; | |
} | |
} |
Hey Chris, thanks for this super awesome guide! ❤️
Please adjust:
- https://gist.github.com/chris-cmsoft/d398cb87907db6f39341df53345d6925#file-dockerfile-L75
Should be commented out:# Install nginx config
- https://gist.github.com/chris-cmsoft/d398cb87907db6f39341df53345d6925#file-dockerfile-L83
Comment out or remove as it crashes the NGINX process:# CMD ["nginx", "-c", "/etc/nginx/nginx.conf"]
. This fixes the issue Mark Foster referred to in Disqus.
All fixed. Thanks for letting me know Peter ! Glad you're enjoying it :)
Hi, this line according to your blog must be changed to:
COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template
Hello!
Great tutorial first of all. It's really helping me solve a client's migration to Kubernetes.
I found that on the Dockerfile the PHP version is outdated and I was getting errors on the build. Can you change it from php:8.0-fpm-alpine
to php:8.1-fpm-alpine
. I also changed the php:8.0-alpine
in the cli step locally to keep it the same version, although it wasn't returning any errors on that part.
Also, on the web_server step, on the blog page, you indicate that the nginx template file should be created in a new folder named docker
. But in the Dockerfile you attempt to copy from the root. So please change COPY nginx.conf.template /etc/nginx/templates/default.conf.template
to COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template
.
Cheers!
Hey Chris, thanks for this super awesome guide! ❤️
Please adjust:
https://gist.github.com/chris-cmsoft/d398cb87907db6f39341df53345d6925#file-dockerfile-L75
Should be commented out:
# Install nginx config
https://gist.github.com/chris-cmsoft/d398cb87907db6f39341df53345d6925#file-dockerfile-L83
Comment out or remove as it crashes the NGINX process:
# CMD ["nginx", "-c", "/etc/nginx/nginx.conf"]
. This fixes the issue Mark Foster referred to in Disqus.