├── .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
--------------------------------------------------------------------------------