Skip to content

Commit a73166e

Browse files
committed
polish nginx-fpm for production
1 parent 486acd3 commit a73166e

File tree

7 files changed

+145
-45
lines changed

7 files changed

+145
-45
lines changed

example-app/entrypoint.sh

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
#!/bin/sh
22
set -e
33

4-
# Check if the storage directory is empty (initial start)
4+
# Initialize storage directory if empty
5+
# -----------------------------------------------------------
6+
# If the storage directory is empty, copy the initial contents
7+
# and set the correct permissions.
8+
# -----------------------------------------------------------
59
if [ ! "$(ls -A /var/www/storage)" ]; then
610
echo "Initializing storage directory..."
711
cp -R /var/www/storage-init/. /var/www/storage
812
chown -R www-data:www-data /var/www/storage
913
fi
1014

15+
# Run Laravel migrations
16+
# -----------------------------------------------------------
17+
# Ensure the database schema is up to date.
18+
# -----------------------------------------------------------
19+
php artisan migrate --force
20+
21+
# Clear and cache configurations
22+
# -----------------------------------------------------------
23+
# Improves performance by caching config and routes.
24+
# -----------------------------------------------------------
25+
php artisan config:cache
26+
php artisan route:cache
27+
1128
# Run the default command
1229
exec "$@"

example-app/routes/web.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,55 @@
66
Log::info('Welcome page visited');
77
return view('welcome');
88
});
9+
10+
Route::get('/health', function () {
11+
$status = [];
12+
13+
// Check Database Connection
14+
try {
15+
DB::connection()->getPdo();
16+
// Optionally, run a simple query
17+
DB::select('SELECT 1');
18+
$status['database'] = 'OK';
19+
} catch (\Exception $e) {
20+
$status['database'] = 'Error';
21+
}
22+
23+
// Check Redis Connection
24+
try {
25+
Cache::store('redis')->put('health_check', 'OK', 10);
26+
$value = Cache::store('redis')->get('health_check');
27+
if ($value === 'OK') {
28+
$status['redis'] = 'OK';
29+
} else {
30+
$status['redis'] = 'Error';
31+
}
32+
} catch (\Exception $e) {
33+
$status['redis'] = 'Error';
34+
}
35+
36+
// Check Storage Access
37+
try {
38+
$testFile = 'health_check.txt';
39+
Storage::put($testFile, 'OK');
40+
$content = Storage::get($testFile);
41+
Storage::delete($testFile);
42+
43+
if ($content === 'OK') {
44+
$status['storage'] = 'OK';
45+
} else {
46+
$status['storage'] = 'Error';
47+
}
48+
} catch (\Exception $e) {
49+
$status['storage'] = 'Error';
50+
}
51+
52+
// Determine overall health status
53+
$isHealthy = collect($status)->every(function ($value) {
54+
return $value === 'OK';
55+
});
56+
57+
$httpStatus = $isHealthy ? 200 : 503;
58+
59+
return response()->json($status, $httpStatus);
60+
});

production/nginx-fpm/.env.example

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
# Sync user ID and group ID to avoid permission issues
2-
UID=1000 # Replace with your user ID
3-
GID=1000 # Replace with your group ID
4-
5-
# Path to your Laravel application
6-
LARAVEL_APP_PATH=/absolute/path/to/your-laravel-app
1+
COMPOSE_PROJECT_NAME=laravel-example-production-nginx-fpm
72

83
# Nginx configuration
9-
# The host where Nginx will be accessible.
10-
NGINX_HOST=localhost
11-
124
# The port on which Nginx will listen. Typically, this would be port 80.
135
NGINX_PORT=80
146

157
# PostgreSQL database configuration
168
# The name of the database to be used by your Laravel application.
17-
DB_DATABASE=laravel
9+
POSTGRES_DATABASE=laravel
1810

1911
# The username for accessing the PostgreSQL database.
20-
DB_USERNAME=laravel
12+
POSTGRES_USERNAME=laravel
2113

2214
# The password for accessing the PostgreSQL database.
23-
DB_PASSWORD=secret
15+
POSTGRES_PASSWORD=secret
16+
17+
# The port on which the PostgreSQL database will be exposed.
18+
POSTGRES_PORT=5432

production/nginx-fpm/compose.yaml

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
services:
22
web:
3-
image: nginx:alpine
43
build:
54
context: ../../example-app
65
dockerfile: ../production/nginx-fpm/nginx/Dockerfile
6+
restart: unless-stopped # Automatically restart unless the service is explicitly stopped
77
volumes:
8-
- laravel-storage:/var/www/storage:ro # Mount the storage volume (in case you want to use storage:link)
8+
# Mount the 'laravel-storage' volume to '/var/www/storage' inside the container.
9+
# -----------------------------------------------------------
10+
# This volume stores persistent data like uploaded files and cache.
11+
# The ':ro' option mounts it as read-only in the 'web' service because Nginx only needs to read these files.
12+
# The 'php-fpm' service mounts the same volume without ':ro' to allow write operations.
13+
# -----------------------------------------------------------
14+
- laravel-storage:/var/www/storage:ro
915
networks:
1016
- laravel
1117
ports:
18+
# Map port 80 inside the container to the port specified by 'NGINX_PORT' on the host machine.
19+
# -----------------------------------------------------------
20+
# This allows external access to the Nginx web server running inside the container.
21+
# For example, if 'NGINX_PORT' is set to '8080', accessing 'http://localhost:8080' will reach the application.
22+
# -----------------------------------------------------------
1223
- "${NGINX_PORT}:80"
13-
environment:
14-
- NGINX_HOST=${NGINX_HOST}
1524
depends_on:
1625
php-fpm:
1726
condition: service_healthy # Wait for php-fpm health check
@@ -21,52 +30,61 @@ services:
2130
build:
2231
context: ../../example-app
2332
dockerfile: ../production/nginx-fpm/php-fpm/Dockerfile
33+
restart: unless-stopped
2434
volumes:
2535
- laravel-storage:/var/www/storage # Mount the storage volume
26-
environment:
27-
- APP_NAME=TestApp
28-
- APP_KEY=base64:y6S7gjyAO/PVAJMBvJSJUCO8qiz92qvcNAPRKH3hpj4=
29-
- APP_DEBUG=true
36+
env_file:
37+
- ../../example-app/.env
3038
networks:
3139
- laravel
3240
healthcheck:
3341
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
3442
interval: 10s
3543
timeout: 5s
3644
retries: 3
37-
# The `depends_on` section tells Docker Compose to
38-
# start the database before your application.
45+
# The 'depends_on' attribute with 'condition: service_healthy' ensures that
46+
# this service will not start until the 'postgres' service passes its health check.
47+
# This prevents the application from trying to connect to the database before it's ready.
3948
depends_on:
4049
postgres:
4150
condition: service_healthy
4251

52+
# The 'php-cli' service provides a command-line interface for running Artisan commands and other CLI tasks.
53+
# -----------------------------------------------------------
54+
# This is useful for running migrations, seeders, or any custom scripts.
55+
# It shares the same codebase and environment as the 'php-fpm' service.
56+
# -----------------------------------------------------------
4357
php-cli:
4458
build:
4559
context: ../../example-app
4660
dockerfile: ../production/nginx-fpm/php-cli/Dockerfile
47-
tty: true # Keep the terminal open
48-
stdin_open: true # Keep stdin open to attach to the container
49-
environment:
50-
- APP_NAME=TestApp
61+
tty: true # Enables an interactive terminal
62+
stdin_open: true # Keeps standard input open for 'docker exec'
63+
env_file:
64+
- ../../example-app/.env
5165
networks:
5266
- laravel
5367

5468
postgres:
5569
image: postgres:16
56-
restart: always
70+
restart: unless-stopped
5771
user: postgres
58-
secrets:
59-
- db-password
6072
ports:
61-
- "5432:5432"
73+
- "${POSTGRES_PORT}:5432"
6274
environment:
63-
- POSTGRES_DB=${DB_DATABASE}
64-
- POSTGRES_USER=${DB_USERNAME}
65-
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
75+
- POSTGRES_DB=${POSTGRES_DATABASE}
76+
- POSTGRES_USER=${POSTGRES_USERNAME}
77+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
6678
volumes:
6779
- postgres-data:/var/lib/postgresql/data
6880
networks:
6981
- laravel
82+
# Health check for PostgreSQL
83+
# -----------------------------------------------------------
84+
# Health checks allow Docker to determine if a service is operational.
85+
# The 'pg_isready' command checks if PostgreSQL is ready to accept connections.
86+
# This prevents dependent services from starting before the database is ready.
87+
# -----------------------------------------------------------
7088
healthcheck:
7189
test: [ "CMD", "pg_isready" ]
7290
interval: 10s
@@ -75,21 +93,28 @@ services:
7593

7694
redis:
7795
image: redis:alpine
96+
restart: unless-stopped # Automatically restart unless the service is explicitly stopped
7897
networks:
7998
- laravel
99+
# Health check for Redis
100+
# -----------------------------------------------------------
101+
# Checks if Redis is responding to the 'PING' command.
102+
# This ensures that the service is not only running but also operational.
103+
# -----------------------------------------------------------
80104
healthcheck:
81105
test: ["CMD", "redis-cli", "ping"]
82106
interval: 10s
83107
timeout: 5s
84108
retries: 3
85109

86110
networks:
111+
# Attach the service to the 'laravel' network.
112+
# -----------------------------------------------------------
113+
# This custom network allows all services within it to communicate using their service names as hostnames.
114+
# For example, 'php-fpm' can connect to 'postgres' by using 'postgres' as the hostname.
115+
# -----------------------------------------------------------
87116
laravel:
88117

89118
volumes:
90119
postgres-data:
91120
laravel-storage:
92-
93-
secrets:
94-
db-password:
95-
file: db-password.txt

production/nginx-fpm/db-password.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

production/nginx-fpm/nginx/Dockerfile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@ RUN npm install && npm run build
2121
FROM nginx:alpine
2222

2323
# Copy custom Nginx configuration
24+
# -----------------------------------------------------------
25+
# Replace the default Nginx configuration with our custom one
26+
# that is optimized for serving a Laravel application.
27+
# -----------------------------------------------------------
2428
COPY nginx.conf /etc/nginx/nginx.conf
2529

26-
# Copy Laravel's public assets (from the builder stage)
30+
# Copy Laravel's public assets from the builder stage
31+
# -----------------------------------------------------------
32+
# We only need the 'public' directory from our Laravel app.
33+
# -----------------------------------------------------------
2734
COPY --from=builder /var/www/public /var/www/public
2835

29-
# Ensure proper permissions for www-data (Nginx default user)
30-
#RUN chown -R www-data:www-data /var/www/public
36+
# Set the working directory to the public folder
37+
WORKDIR /var/www/public
3138

32-
# Expose port 80
39+
# Expose port 80 and start Nginx
3340
EXPOSE 80
34-
3541
CMD ["nginx", "-g", "daemon off;"]

production/nginx-fpm/php-fpm/Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions
8181
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
8282
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/
8383

84-
# Use the default production configuration for PHP runtime arguments
84+
# Use the recommended production PHP configuration
85+
# -----------------------------------------------------------
86+
# PHP provides development and production configurations.
87+
# Here, we replace the default php.ini with the production
88+
# version to apply settings optimized for performance and
89+
# security in a live environment.
90+
# -----------------------------------------------------------
8591
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
8692

8793
# Enable PHP-FPM status page by modifying zz-docker.conf with sed

0 commit comments

Comments
 (0)