How to Deploy a Laravel Application on Render: Key Lessons
After I built my Asset Processing Pipeline’s first product, the Image Resizer, I figured that I had to deploy it on the internet so that others can use it. In addition to that, I wanted to understand how to deploy a monolith like Laravel. I had built Laravel apps in the past but in that era, I was not in charge of deploying because deployment had to do with copying files and uploading through a FTP to the server you had just bought on WhogoHost.
Now, times have changed, we have some dozen-and-one ways to deploy an application on the interwebs. Among all of these options, I chose Render. I chose Render because it does not affect my pocket (currently broke) and it usually has a simple process. I have used render to deploy almost all of my side projects that have to do with a web service (backend). This was my first time deploying a Lravel application myself, let alone on Render, so, it is safe to say it was a roller coaster.
Now, to the main reason you opened up this article, the steps:
1. Create NGINX configuration
I bunched all deployment related files in a folder called deploy. In that folder, I created an NGINX configuration file called nginx.conf. The need for NGINX is to serve as the web server for PHP. php-fpm only handles PHP code processes, it does not handle web requests. Another option is to use Apache.
My NGINX configuration looks like so
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/html/public;
index index.php;
# Security: Hide NGINX version
server_tokens off;
# Laravel routing
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP processing
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
# Security: Block hidden files
location ~ /\. {
deny all;
}
# Performance: Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
If you need to add more configurations based on the type of application you are deploying, you should check out the NGINX Documentation. For example, in my own deployment I needed to increase the size of files allowed to be up to 20MB, the initial default is 10MB. I added this:
# Increased upload size for file uploads
client_max_body_size 20M;
2. Bash Script
You need a bash script to serve as an entrypoint script that runs when Render wants to start your application. In the case of deploying this Laravel app, this bash script will run at the end of the Dockerfile setup. It should handle: caching configuration, running migrations, running seeders, running any other scripts, running a supervisor manager, etc.
In my case, my bash script looked like this:
echo "Clearing config cache..."
php artisan config:clear
echo "Caching config..."
php artisan config:cache
echo "Caching routes..."
php artisan route:cache
echo "Running migrations..."
php artisan migrate --force
echo "Starting supervisord..."
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
The echo’s are there to help with debugging and seeing logs on the deployment GUI that Render has.
3. Supervisor Manager Configuration
In the above script, you might have been wondering what this line meant:
echo "Starting supervisord..."
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
It is called a supervisor. It is a process control system or a process manager. To understand what I mean, I would paint the scenerio I need it for. The application I deployed is an Asset Processing Pipeline which needs Laravel Queue (for dispatching background jobs) and Laravel Reverb (for real-time event communication from the Laravel backend to the Vue frontend) to run alongside the Laravel server. Because I need all of these processes, I need something that would help me handle the following for all these processes:
- auto-restart
- ensure all processes are up
- logging for all processes are in one place
- start up and shutdown of all processes in one place rather than in multiple
My supervisor file looks like:
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:php-fpm]
command=php-fpm --nodaemonize
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=0
priority=10
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=0
priority=20
startsecs=2
[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/queue.log
stopwaitsecs=3600
[program:laravel-reverb]
command=php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080 --hostname=0.0.0.0
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/reverb.log
This is totally optional. If all you need is just a Laravel Server, the NGINX + PHP-FPM setup is enough.
4. Dockerize your application
At the end, the whole setup comes together by creating a Dockerfile, preferrably in the root of the application. This is to containerize your application. Think of it has packaging your application with all the needed packages and configuration for it to start running.
Below is a peek into the Dockerfile I used to deploy my own application
FROM php:8.4-fpm-alpine
# Install system dependencies
RUN apk add --no-cache \
bash \
nginx \
supervisor \
...
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install \
pdo \
pdo_pgsql \ # For Postgres DB
...
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy application files
COPY . .
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Install frontend dependencies and build assets
RUN npm ci
# Accept build arguments for asset URLs
ARG APP_URL=xxx
ARG ASSET_URL=xxx
# Set as environment variables for the build
ENV APP_URL=${APP_URL}
ENV ASSET_URL=${ASSET_URL}
ENV NODE_ENV=production
# Build frontend assets during image creation
RUN npm run build
# Verify build output exists
RUN ls -la /var/www/html/public/build || echo "Warning: Build directory not found"
# Set permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
# Copy nginx config
COPY deploy/nginx.conf /etc/nginx/nginx.conf
# Copy supervisor config (optional)
COPY deploy/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Create log directories
RUN mkdir -p /var/log/supervisor /var/log/nginx /var/run
# Image config
ENV APP_ENV=production
ENV APP_DEBUG=false
ENV LOG_CHANNEL=stderr
# Expose ports
EXPOSE 80 8080
CMD ["./deploy/start.sh"]
5. Push Docker Image to Docker Hub
The Dockerfile you have when built will create a docker image which will be needed on Render to run your application. In order for you to be able to push your image to Docker Hub, follow these steps:
Navigate to Docker Hub to create a public repository for the project
Build your Dockerfile into an image and push, for that you need this command
docker buildx build --platform linux/amd64 \
--push \
--build-arg APP_URL=xxx \
--build-arg ASSET_URL=xxx \
-t username/repository-name .
The above command is for Mac. For other OS, you can check out this video from timetamp 3:30, or if you prefer video content.
6. Pull on Render and Deploy
Provided the above step is completed, now head over to Render. Create an account if you don’t have one. Create a new project, if you don’t have any. If you already have one, continue to use that.
Create a new web service
Under “Configure“, select “Existing Image“
In the “Image URL“ field, enter
username/repository-nameas you have in your Docker Hub.Render will detect automatically and then move you on to enter your environment variables
Set needed environment variables and then click on “Deploy Web Service“ to deploy.
Conclusion
Deploying a Laravel application is different from every other application because of its unique setup. You need to have a proper production server in NGINX or Apache to be able to listen to and act on HTTP requests coming to your application. You also need a script setup much like any other deployment you would do with a backend application because you may need to run some migrations, seeders and then start up the server.
The method I used here is to package your application in a Docker container, push the image to Docker Hub, pull the Image on Render (or any other deployment platform you are using) and then deploy with proper environment variable.
Cheers.


