I have a Laravel project folder (as part of a Git repo) on a Linux server that I've been running Nginx on and just directly serving, but now I want to containerize that project with Docker. I also have a React project folder in the root directory of the repo. The Laravel project is in the folder titled API and the React project is in the folder titled speedcart-react.
I created all of the following files in my API folder for the Laravel project in discussion:
Dockerfile:
# Use the official PHP image as a base image
FROM php:8.2-fpm
# Set working directory (all subsequent commands will be run in this directory)
WORKDIR /var/www/SpeedCart/API
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
sqlite3 \
libsqlite3-dev
# Clear cache (This cleans up the package cache to reduce the size of the Docker image)
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions required by Laravel
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd pdo_sqlite
# Install Composer (copies the composer binary from the latest Composer image
# into your Docker image, making Composer available for dependency management)
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Copy existing application directory contents from working directory
# on host to /var/www/SpeedCart/API in the container
COPY . /var/www/SpeedCart/API
# Copy existing application directory permissions
COPY --chown=www-data:www-data . /var/www/SpeedCart
# Change current user to www-data (used by web servers to improve security)
USER www-data
# Expose port 9000 (the default port for PHP-FPM to listen on) and start php-fpm server
# (Even if you are using Nginx as a reverse proxy, PHP-FPM will still need to be exposed
# internally within the Docker network, which is why we need EXPOSE 9000)
EXPOSE 9000
CMD ["php-fpm"]
docker-compose.yml:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
image: laravel-app
container_name: laravel-app
restart: unless-stopped
working_dir: /var/www/SpeedCart/API
volumes:
- .:/var/www/SpeedCart/API
- ./php.ini:/usr/local/etc/php/conf.d/local.ini
webserver:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
# Map host ports to ports in container
ports:
- "80:80"
- "443:443"
volumes:
- .:/var/www/SpeedCart/API
- ./default.conf:/etc/nginx/conf.d/default.conf
networks:
laravel:
driver: bridge
default.conf:
server {
listen 80;
server_name api.speedcartapp.com;
location / {
proxy_pass http://172.18.0.2:9000; # Replace with your container name or service name
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SSL configuration
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/api.speedcartapp.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/api.speedcartapp.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
I have tried running docker-compose up -d --build
after stopping Nginx with systemctl, then got the IP of the docker container that was running after that via running sudo docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' laravel-app
and I put that IP address in the configuration file.
I tried adding the Nginx configuration file to /etc/nginx/sites-available (instead of the /var/www/SpeedCart/API/default.conf file location I was using before) and linked to that in /etc/nginx/sites-enabled since I thought that was the problem, but that didn't work. At the moment, my configuration file looks like this:
server {
listen 80;
server_name api.speedcartapp.com;
location / {
proxy_pass http://172.18.0.3:9000; # Replace with your container name or service name
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SSL configuration
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/api.speedcartapp.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/api.speedcartapp.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
I have run sudo nginx -t
in the sites-available directory and it was all successful information. The only other configuration files I have are shopfast-react (old name but it's what's running for the speedcart-react folder in my repo), which looks like this:
server {
listen 80;
server_name speedcartapp.com www.speedcartapp.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
# server_name 139.144.239.217; # your server IP
server_name speedcartapp.com www.speedcartapp.com;
ssl_certificate /etc/letsencrypt/live/speedcartapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/speedcartapp.com/privkey.pem;
root /var/www/SpeedCart/speedcart-react/build;
index index.html;
location / {
try_files $uri /index.html;
}
# Redirect HTTP to HTTPS
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
location ~ /\.well-known/acme-challenge/ {
allow all;
root /var/www/SpeedCart/speedcart-react/build;
}
location /api {
# Assuming your PHP file is named api.php
try_files $uri $uri/ /api.php?$args;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust the version based on your PHP-FPM configuration
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
add_header Access-Control-Allow-Origin "http://localhost:3000";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
location ~ /\. {
deny all;
}
location ~* /speedcart-react/(.*) {
alias /var/www/speedcart-react/build/$1;
try_files $uri $uri/ /speedcart-react/index.html;
}
# ... other configurations if any ...
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust the version based on your PHP-FPM configuration
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PROJECT_ROOT /var/www/SpeedCart; # Add this line
include fastcgi_params;
}
}
and default which looks like this:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.php;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Adjust the socket path
# Add the following line to set the environment variable
fastcgi_param PROJECT_ROOT /var/www/html;
include fastcgi_params;
}
# ... other configuration ...
}
When I run sudo docker ps
to check that the container is running, I get this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3ee6e572d623 laravel-app "docker-php-entrypoi…" 22 minutes ago Up 21 minutes 9000/tcp laravel-app
This is what my routes.php in /var/www/SpeedCart/API/public looks like for my actual routes:
<?php
// Note: We need to move almost all of this content to api.php for clarity ASAP
use App\Libraries\Database\Database;
use App\Libraries\Logging\Loggable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use App\Http\Middleware\GoogleAuthentication; // This brings in our middleware to ensure authentication prior to user actions actually being done
// This is primarily for testing; since it's middleware, it doesn't usually get directly contacted
Route::post('/auth/google', function () {
// No code necessary here; we just want to test the middleware
Log::error("Finished executing GoogleAuthentication middleware"); // Why isn't this logging?
return response()->json([
'status' => 'success',
'message' => 'Authentication successful',
], 200);
})->middleware(GoogleAuthentication::class);
Route::get('/phpinfo', function () {
phpinfo();
});
//use App\Http\Controllers\Api\UserController;
use App\Http\Controllers\Api\RouteController;
use App\Http\Controllers\Api\ShoppingListController;
use App\Http\Controllers\Api\GroceryItemController;
//Route::apiResource('users', UserController::class);
Route::apiResource('routes', RouteController::class);
//Route::apiResource('shopping-lists', ShoppingListController::class);
// Middleware for authentication endpoint
Route::post('/shopping-lists', [ShoppingListController::class, 'store'])
->middleware(GoogleAuthentication::class);
// Route for retrieving all shopping list titles (used for Dashboard page)
Route::get('/shopping-lists', [ShoppingListController::class, 'getUserShoppingLists'])
->middleware(GoogleAuthentication::class);
// Route for retrieving shopping list title for a given ID
Route::get('/shopping-lists/{id}', [ShoppingListController::class, 'show'])
->middleware(GoogleAuthentication::class);
// Route for retrieving all items for a given shopping list ID
Route::get('/grocery-items/{id}', [GroceryItemController::class, 'show'])
->middleware(GoogleAuthentication::class);
// Route for deleting a shopping list
Route::delete('/shopping-lists/{id}', [ShoppingListController::class, 'destroy'])
->middleware(GoogleAuthentication::class);
Route::post('grocery-items', [GroceryItemController::class, 'store'])
->middleware(GoogleAuthentication::class);
// Route for updating shopping list title
Route::put('/shopping-lists/{id}', [ShoppingListController::class, 'update'])
->middleware(GoogleAuthentication::class);
// Route for updating grocery items
Route::put('/grocery-items/{id}', [GroceryItemController::class, 'update'])
->middleware(GoogleAuthentication::class);
// This is also needed for "updating" a grocery list because some items could be deleted
Route::delete('/grocery-items/{id}', [GroceryItemController::class, 'destroy'])
->middleware(GoogleAuthentication::class);
I can't figure out why api.speedcartapp.com won't work; I tried /phpinfo as the path because that one is an actual page (whereas my other endpoints are just REST APIs), but I keep getting 502 Bad Gateway
and the Nginx error.log file looks like this every time you try that:
2024/06/15 16:38:27 [error] 3629981#3629981: *25 recv() failed (104: Unknown error) while reading response header from upstream, client: 167.94.138.40, server: api.speedcartapp.com, request: "GET / HTTP/1.1", upstream: "http://172.18.0.3:9000/", host: "139.144.239.217"
I've tried looking everywhere and I just want to use api.speedcartapp.com for my Laravel endpoints with Docker containers.
Edit: I made a container for php-fpm and I'm working on a container for nginx, but I keep getting this error in the logs whenever the container tries starting up: 2024/06/16 22:18:13 [emerg] 1#1: open() "/etc/nginx/snippets/fastcgi-php.conf" failed (2: No such file or directory) in /etc/nginx/conf.d/default.conf:24 nginx: [emerg] open() "/etc/nginx/snippets/fastcgi-php.conf" failed (2: No such file or directory) in /etc/nginx/conf.d/default.conf:24
I looked at another StackOverflow post on this error which said that the volumes list caused the container to actually ignore the file within the container (because it should consult the host machine instead for all volumes), but I even got rid of that and retried building using this docker-compose.yml file:
version: '3.8'
services:
nginx:
build:
context: .
dockerfile: docker/nginx/Dockerfile
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/ssl:/etc/nginx/ssl # Ensure this directory exists in your project
depends_on:
- app
app:
build:
context: .
dockerfile: docker/php/Dockerfile
volumes:
- .:/var/www/SpeedCart/API
expose:
- "9000"
I even included a RUN command in the Dockerfile that runs ls -la on /etc/nginx/snippets after all the copy commands and got this output, showing the file considered missing indeed does exist within the container:
Step 6/7 : RUN ls -la /etc/nginx/snippets
---> Running in bc1be3860516
total 12
drwxr-xr-x 2 root root 4096 Jun 17 20:58 .
drwxr-xr-x 1 root root 4096 Jun 17 20:58 ..
-rw-rw-r-- 1 root root 249 Jun 16 18:26 fastcgi-php.conf
I figured this out; I needed to create two container images to run two instances, one for the web server reverse proxy to get HTTPS to work and one for the actual Laravel code to run based on a PHP FPM image. I also had to make sure environment variables were included in the image creation process and I set the database as a volume (along with some other volumes like the log file for logging output).