├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── README.md ├── config └── etc │ ├── nginx │ └── sites-available │ │ └── default │ ├── php │ └── 8.4 │ │ ├── cli │ │ └── conf.d │ │ │ └── y-php.ini │ │ └── fpm │ │ └── pool.d │ │ └── y-www.conf │ ├── supervisor │ ├── conf.d-temp │ │ ├── jobs.conf │ │ ├── nginx.conf │ │ ├── php-fpm.conf │ │ └── scheduler.conf │ └── conf.d │ │ └── .keep │ └── supervisord.conf ├── databases └── sqlserver.sh ├── octopus.png └── start.sh /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | tags: 8 | - "*" 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up QEMU 18 | uses: docker/setup-qemu-action@v3 19 | 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Login to DockerHub 24 | uses: docker/login-action@v3 25 | with: 26 | username: ${{ secrets.DOCKERHUB_USERNAME }} 27 | password: ${{ secrets.DOCKERHUB_TOKEN }} 28 | 29 | - name: Docker meta 30 | id: meta 31 | uses: docker/metadata-action@v5 32 | with: 33 | images: robsontenorio/laravel 34 | flavor: latest=false 35 | tags: | 36 | type=ref,event=tag 37 | type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=latest 38 | 39 | - name: Build and push 40 | uses: docker/build-push-action@v6 41 | with: 42 | context: . 43 | platforms: linux/amd64,linux/arm64 44 | push: true 45 | tags: ${{ steps.meta.outputs.tags }} 46 | 47 | - name: Docker Hub Description 48 | uses: peter-evans/dockerhub-description@v4 49 | with: 50 | username: ${{ secrets.DOCKERHUB_USERNAME }} 51 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 52 | repository: robsontenorio/laravel 53 | short-description: ${{ github.event.repository.description }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | LABEL maintainer="Robson Tenório" 4 | LABEL site="https://github.com/robsontenorio/laravel-docker" 5 | 6 | ENV TZ=UTC 7 | ENV LANG="C.UTF-8" 8 | ENV DEBIAN_FRONTEND=noninteractive 9 | ARG CONTAINER_ROLE=APP 10 | ENV CONTAINER_ROLE=${CONTAINER_ROLE} 11 | 12 | WORKDIR /var/www/app 13 | 14 | RUN apt update \ 15 | # Add PHP 8.4 repository 16 | && apt install -y software-properties-common && add-apt-repository ppa:ondrej/php \ 17 | # PHP extensions 18 | && apt install -y \ 19 | php8.4-bcmath \ 20 | php8.4-cli \ 21 | php8.4-curl \ 22 | php8.4-fpm \ 23 | php8.4-gd \ 24 | php8.4-intl \ 25 | php8.4-mbstring \ 26 | php8.4-mysql \ 27 | php8.4-redis \ 28 | php8.4-sockets \ 29 | php8.4-sqlite3 \ 30 | php8.4-pcov \ 31 | php8.4-pgsql \ 32 | php8.4-opcache \ 33 | php8.4-xml \ 34 | php8.4-zip \ 35 | # Extra 36 | curl \ 37 | git \ 38 | gnupg \ 39 | htop \ 40 | nano \ 41 | nginx \ 42 | supervisor \ 43 | unzip \ 44 | zsh 45 | 46 | # Other database drivers 47 | COPY databases /tmp/databases 48 | RUN chmod a+x -R /tmp/databases 49 | RUN /tmp/databases/sqlserver.sh 50 | 51 | # Composer 52 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer 53 | 54 | # Node, NPM, Yarn 55 | RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt install -y nodejs && npm -g install yarn --unsafe-perm 56 | 57 | # Create user/group with id/uid (1000/1000) 58 | RUN userdel ubuntu 59 | RUN groupadd -f -g 1000 appuser 60 | RUN useradd -u 1000 -m -d /home/appuser -g appuser appuser 61 | 62 | # Config files 63 | COPY --chown=appuser:appuser start.sh /usr/local/bin/start 64 | COPY --chown=appuser:appuser config/etc /etc 65 | COPY --chown=appuser:appuser config/etc/php/8.4/cli/conf.d/y-php.ini /etc/php/8.4/fpm/conf.d/y-php.ini 66 | 67 | # Permissions for start script 68 | RUN chmod a+x /usr/local/bin/start 69 | 70 | # Required for php-fpm and nginx as non-root user 71 | RUN mkdir -p /run/php 72 | RUN chown -R appuser:appuser /var/www/app /var/log /var/lib /run 73 | RUN chmod -R 777 /var/log /var/lib /run 74 | 75 | # Switch to non-root user 76 | USER appuser 77 | 78 | # Laravel Installer 79 | RUN composer global require laravel/installer && composer clear-cache 80 | 81 | # OhMyZsh (better than "bash") 82 | RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 83 | 84 | # Add composer to PATH 85 | RUN echo 'export PATH="$PATH:$HOME/.config/composer/vendor/bin"' >> ~/.zshrc 86 | 87 | # Add SQL Tools to PATH 88 | RUN echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.zshrc 89 | 90 | # Nginx (8080), Node (3000/3001), Laravel Dusk (9515/9773) 91 | EXPOSE 8080 8000 3000 3001 9515 9773 92 | 93 | # Start services through "supervisor" based on "CONTAINER_ROLE". See "start.sh". 94 | CMD ["/usr/local/bin/start"] 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 | 8 | 9 | 10 |

11 | 12 | # Laravel Docker Image (FPM) 13 | 14 | It provides a flexible strategy to assign roles to specific containers by re-using same image. 15 | 16 | When handling massive amount of process the best option is to split into multiple containers. This way, each container has a specific role on servers and you can scale it independently. 17 | 18 | ## Features 19 | 20 | - Nginx 21 | - PHP 22 | - FPM and common extensions 23 | - Composer 24 | - Laravel Installer 25 | - Node 26 | - Yarn 27 | - Npm 28 | - Database drivers 29 | - Mysql 30 | - Postgres 31 | - Sqlite 32 | - Supervisor 33 | - All services are started through `supervisord` 34 | - Extra 35 | - Zsh 36 | - Git 37 | - And more ... 38 | 39 | ## Container role 40 | 41 | Assign specific role to a container. 42 | **Laravel Horizon is mandatory for JOB or ALL roles.** 43 | 44 | 45 | | Value | Description | 46 | | --------------- | ----------- | 47 | | APP *(default)* | php-fpm + nginx 48 | | JOBS | php-fpm + horizon queue + scheduler 49 | | ALL | all in one 50 | 51 | ### Optional 52 | 53 | | Key | Description | 54 | | --------------------------- | ----------- | 55 | | GITHUB_OAUTH_KEY | Needed due to github rate limit | 56 | 57 | 58 | ## Local development 59 | 60 | `CONTAINER_ROLE=ALL` . This is the all in one strategy on same container. 61 | 62 | ```yaml 63 | # docker-compose.yml 64 | 65 | services: 66 | # nginx + php-fpm + horizon queue + scheduler 67 | app: 68 | image: robsontenorio/laravel 69 | environment: 70 | - CONTAINER_ROLE=ALL 71 | volumes: 72 | - .:/var/www/app 73 | ports: 74 | - 8080:8080 75 | 76 | # Other services here like mysql, redis ... 77 | ``` 78 | 79 | Split into multiple containers. 80 | 81 | ```yaml 82 | # docker-compose.yml 83 | 84 | services: 85 | # php-fpm + nginx 86 | app: 87 | image: robsontenorio/laravel 88 | environment: 89 | - CONTAINER_ROLE=APP 90 | volumes: 91 | - .:/var/www/app 92 | ports: 93 | - 8080:8080 94 | 95 | # php-fpm + horizon queue + scheduler 96 | jobs: 97 | image: robsontenorio/laravel 98 | environment: 99 | - CONTAINER_ROLE=JOBS 100 | volumes: 101 | - .:/var/www/app 102 | 103 | # Other services like mysql, redis ... 104 | ``` 105 | 106 | 107 | ## Production 108 | 109 | This only applies if your deployment platform is based on docker. 110 | 111 | This image relies on `/usr/local/bin/start` script to bootstrap all services. 112 | 113 | Consider this setup. 114 | 115 | ```bash 116 | .docker/ 117 | |__ deploy.sh # production only 118 | |__ Dockerfile # production only 119 | |__ docker-compose.yml # development only 120 | 121 | app/ 122 | |__ ... 123 | ``` 124 | 125 | A good idea is to have a `deploy.sh` script to run any aditional commands before container startup on target deployment platform. 126 | 127 | ```bash 128 | #!/bin/sh 129 | set -e 130 | 131 | echo 'Starting deployment tasks ...' 132 | 133 | php artisan config:cache 134 | php artisan migrate --seed --force 135 | 136 | # more commands ... 137 | 138 | echo 'Done!' 139 | ``` 140 | 141 | So, on `Dockerfile` 142 | 143 | ```dockerfile 144 | FROM robsontenorio/laravel 145 | 146 | COPY . . 147 | RUN chmod a+x .docker/deploy.sh 148 | 149 | # Run deployment tasks before start services 150 | CMD ["/bin/sh", "-c", ".docker/deploy.sh && /usr/local/bin/start"] 151 | ``` 152 | 153 | ### Container role 154 | 155 | It really depends on platform you will deploy. All you need is to set an environment variable to container. 156 | 157 | - CONTAINER_ROLE=APP (default, no need to set) 158 | - CONTAINER_ROLE=JOBS 159 | - CONTAINER_ROLE=ALL 160 | 161 | ## Gitlab example 162 | 163 | ```yaml 164 | image: robsontenorio/laravel # <--- Will be used in all steps 165 | 166 | stages: 167 | - build 168 | - test 169 | - deploy 170 | 171 | # Install PHP dependencies 172 | composer: 173 | stage: build 174 | ... 175 | 176 | # Install JS dependencies 177 | yarn: 178 | stage: build 179 | ... 180 | 181 | # PHP tests 182 | phpunit: 183 | stage: test 184 | dependencies: 185 | - composer 186 | - yarn 187 | ... 188 | 189 | 190 | # Build production final docker image and deploy it (optional) 191 | production: 192 | stage: deploy 193 | image: docker:latest 194 | only: 195 | - tags 196 | script: 197 | - docker login 198 | - docker build 199 | - docker push 200 | ``` 201 | -------------------------------------------------------------------------------- /config/etc/nginx/sites-available/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen [::]:8080 default_server; 3 | listen 8080 default_server; 4 | server_name _; 5 | 6 | root /var/www/app/public; 7 | index index.php index.html; 8 | 9 | charset utf-8; 10 | client_max_body_size 16m; 11 | 12 | error_page 404 /index.php; 13 | 14 | location = /favicon.ico { access_log off; log_not_found off; } 15 | location = /robots.txt { access_log off; log_not_found off; } 16 | 17 | # Default 18 | location / { 19 | try_files $uri $uri/ /index.php?$query_string; 20 | } 21 | 22 | # Prevent cache JS for livewire routes, and make it as a normal route 23 | location ^~ /livewire { 24 | try_files $uri $uri/ /index.php?$query_string; 25 | } 26 | 27 | # PHP 28 | location ~ \.php$ { 29 | fastcgi_pass unix:/home/appuser/php8.4-fpm.sock; 30 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 31 | include fastcgi_params; 32 | fastcgi_buffers 16 16k; 33 | fastcgi_buffer_size 32k; 34 | } 35 | 36 | # Prevent access "." files 37 | location ~ /\.(?!well-known).* { 38 | deny all; 39 | } 40 | 41 | # Cache 42 | location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|eot)$ { 43 | expires 5d; 44 | } 45 | 46 | # Enable gzip 47 | gzip on; 48 | gzip_vary on; 49 | gzip_proxied any; 50 | gzip_comp_level 6; 51 | gzip_buffers 16 8k; 52 | gzip_http_version 1.1; 53 | gzip_min_length 256; 54 | gzip_types 55 | application/atom+xml 56 | application/geo+json 57 | application/javascript 58 | application/x-javascript 59 | application/json 60 | application/ld+json 61 | application/manifest+json 62 | application/rdf+xml 63 | application/rss+xml 64 | application/xhtml+xml 65 | application/xml 66 | font/eot 67 | font/otf 68 | font/ttf 69 | image/svg+xml 70 | text/css 71 | text/javascript 72 | text/plain 73 | text/xml; 74 | } 75 | -------------------------------------------------------------------------------- /config/etc/php/8.4/cli/conf.d/y-php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | log_errors=On 3 | upload_max_filesize = 16M 4 | post_max_size = 16M 5 | memory_limit = 2048M 6 | 7 | ; Disable pcov extension by default 8 | ; Eneable it on runtime, ex: "php -d pcov.enabled=1 vendor/bin/phpunit" 9 | pcov.enabled=0 10 | 11 | [www] 12 | ; disable access log for FPM 13 | access.log = /dev/null -------------------------------------------------------------------------------- /config/etc/php/8.4/fpm/pool.d/y-www.conf: -------------------------------------------------------------------------------- 1 | [www] 2 | ; change user, group and socket path 3 | user = appuser 4 | group = appuser 5 | listen.owner = appuser 6 | listen.group = appuser 7 | listen = /home/appuser/php8.4-fpm.sock 8 | clear_env = no 9 | 10 | ; fpm tuning 11 | pm.max_requests = 500 12 | pm.max_children = 100 13 | pm.start_servers = 10 14 | pm.min_spare_servers = 5 15 | pm.max_spare_servers = 20 16 | pm.process_idle_timeout = 10s; 17 | -------------------------------------------------------------------------------- /config/etc/supervisor/conf.d-temp/jobs.conf: -------------------------------------------------------------------------------- 1 | # Process the job queue 2 | [program:laravel-horizon] 3 | process_name=%(program_name)s 4 | command=php /var/www/app/artisan horizon --quiet 5 | autostart=true 6 | autorestart=true 7 | redirect_stderr=true 8 | stopwaitsecs=3600 -------------------------------------------------------------------------------- /config/etc/supervisor/conf.d-temp/nginx.conf: -------------------------------------------------------------------------------- 1 | [program:nginx] 2 | command=nginx -g 'daemon off;' 3 | stdout_logfile=/dev/stdout 4 | stdout_logfile_maxbytes=0 5 | stderr_logfile=/dev/stderr 6 | stderr_logfile_maxbytes=0 7 | autostart=true 8 | autorestart=true 9 | startretries=0 -------------------------------------------------------------------------------- /config/etc/supervisor/conf.d-temp/php-fpm.conf: -------------------------------------------------------------------------------- 1 | # PHP FPM 2 | [program:php-fpm] 3 | command=php-fpm8.4 -F 4 | stdout_logfile=/dev/stdout 5 | stdout_logfile_maxbytes=0 6 | stderr_logfile=/dev/stderr 7 | stderr_logfile_maxbytes=0 8 | autostart=true 9 | autorestart=true 10 | startretries=0 -------------------------------------------------------------------------------- /config/etc/supervisor/conf.d-temp/scheduler.conf: -------------------------------------------------------------------------------- 1 | # Command scheduler 2 | [program:laravel-scheduler] 3 | command=sh -c "while [ true ]; do (cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1); sleep 60; done" 4 | redirect_stderr=true 5 | autostart=true 6 | autorestart=true 7 | startretries=0 8 | -------------------------------------------------------------------------------- /config/etc/supervisor/conf.d/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robsontenorio/laravel-docker/d1336fed52110869011413406ecca89e04377ba9/config/etc/supervisor/conf.d/.keep -------------------------------------------------------------------------------- /config/etc/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/null 4 | logfile_maxbytes=0 5 | pidfile=/home/appuser/supervisor.pid 6 | # loglevel=debug 7 | 8 | # It is empty. Conf will be handled by "start.sh" script 9 | [include] 10 | files = /etc/supervisor/conf.d/*.conf -------------------------------------------------------------------------------- /databases/sqlserver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | ################################################## 5 | # SQLSERVER DRIVERS + TOOLS # 6 | ################################################## 7 | 8 | apt install -y php8.4-dev unixodbc-dev wget 9 | 10 | wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg 11 | wget -qO- https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | tee /etc/apt/sources.list.d/mssql-release.list 12 | apt update 13 | ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18 14 | 15 | pecl channel-update pecl.php.net 16 | pecl install sqlsrv pdo_sqlsrv 17 | printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/8.4/mods-available/sqlsrv.ini 18 | printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/8.4/mods-available/pdo_sqlsrv.ini 19 | phpenmod sqlsrv pdo_sqlsrv 20 | 21 | apt purge php8.4-dev -y && apt-get autoremove -y -------------------------------------------------------------------------------- /octopus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robsontenorio/laravel-docker/d1336fed52110869011413406ecca89e04377ba9/octopus.png -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | role=${CONTAINER_ROLE} 5 | 6 | echo -e " 7 | 8 | ********************************************************************************* 9 | 10 | ==> Starting \"robsontenorio/laravel\" image for CONTAINER_ROLE = \"$role\" ... 11 | 12 | APP (default) => App webserver (nginx + php-fpm). 13 | JOBS => Queued jobs + scheduled commands (schedule:run). 14 | ALL => APP + JOBS 15 | 16 | ********************************************************************************* 17 | 18 | " 19 | cd /etc/supervisor/conf.d-temp 20 | 21 | if [ "$role" = "APP" ]; then 22 | cp nginx.conf ../conf.d/nginx.conf 23 | cp php-fpm.conf ../conf.d/php-fpm.conf 24 | cp scheduler.conf ../conf.d/scheduler.conf 25 | elif [ "$role" = "JOBS" ]; then 26 | cp php-fpm.conf ../conf.d/php-fpm.conf 27 | cp jobs.conf ../conf.d/jobs.conf 28 | elif [ "$role" = "ALL" ]; then 29 | cp nginx.conf ../conf.d/nginx.conf 30 | cp php-fpm.conf ../conf.d/php-fpm.conf 31 | cp jobs.conf ../conf.d/jobs.conf 32 | cp scheduler.conf ../conf.d/scheduler.conf 33 | fi 34 | 35 | supervisord -c /etc/supervisord.conf --------------------------------------------------------------------------------