(This is part of a larger guide to deploying a Laravel and Vue.js web application.)
This article covers deploying a Laravel application.
Before beginning, you should have performed the application-specific server setup covered in the previous phase of the tutorial.
This tutorial will use a deployment workflow called zero-downtime redeployment. We’ll cover this in detail when automating redeployment in the next phase of the tutorial, but we have to set up a directory structure supporting zero-downtime redeployment now.
If you want to read an explanation of this directory structure, open the details/summary elements below. Feel free to skip if impatient.
For orientation, the directory structure after working through this section should look something like this:
/srv/www/laravel/
├── releases/
│ └── initial/
│ ├── app/
│ ├── bootstrap/
│ ├── config/
│ ├── ...
│ ├── .env -> ../../shared/.env
│ └── storage/ -> ../../shared/storage/
├── active -> releases/initial/ # symlink to initial release
└── shared/
├── .env
└── storage/
The releases/
directory holds releases of your app.
active/
is a symlink pointing to the currently active release (i.e. the release served to the public Web).
When you update your app with a new release, you’ll update the active
symlink to point to the new release directory.
We’ll cover this more thoroughly when covering automated zero-downtime redeployment in the last phase of the tutorial.
The shared/
directory contains your app’s .env
file, storage/
directory, database.sqlite
file if using SQLite, and any other files that are shared by all releases and/or not normally tracked by Git.
This structure keeps these shared files decoupled from individual releases of your app, letting you, for example, keep using the same database and environment file when you update your app’s code.
It also makes practical sense—remember that the .env
and database.sqlite
files (and often the storage
directory, too) are Git-ignored in typical Laravel projects, and thus would not be uploaded when you git push
your code to your server.
This directory structure makes redeployments easy—you upload the new release’s code into the releases
directory, link shared files into place, and update active
to point to the new release.
We’ll go through this deployment process manually in this article, then automate it in the next phase of the tutorial.
storage/
directory
In this guide I’ve decided to share the storage/
directory between releases of your app.
This seems to be preferred setup for Laravel apps using zero-downtime redeployment, but there is still some confusion online about the best way to manage the storage/
directory between deployments.
The great benefit to sharing storage/
is that all user uploads, logs, cache files, and other files generated by your application (which should all be in storage/
) will persist between redeployments.
The downside (which really isn’t that a big deal) is the need to manually remove the boilerplate release-specific storage/
directory during redeployments (or just entirely Git-ignore the storage/
directory, so you’re not even pushing it from your dev machine to your server).
Feel free to modify the instructions in this guide if you have a different, preferred way of handling the storage/
directory between deployments.
In ~/.ssh/config
on your development machine, create two new SSH Host entries, one to access your server over SSH, and one to push code to your server over SSH using Git:
# This entry is for SSH access to your server
Host laravel
# This should be your server's IP address
HostName 1.2.3.4
# This is the user you'll later create to administer your web app
User laravel
# This should be the full path to the private SSH key you'll use to access your server
IdentityFile ~/.ssh/LaravelApp_id_ed25519
# This entry is to push code to the server over SSH using Git
Host laravel_git
# This should be your server's IP address
HostName 1.2.3.4
# This should be the full path to the private SSH key you'll use to access your server
IdentityFile ~/.ssh/LaravelApp_id_ed25519
# Leave this as is
IdentitiesOnly yes
The entries have separate purposes:
ssh laravel
instead of having to type ssh [email protected] -i ~/.ssh/LaravelApp_id_ed25519
.It’s fine that both entries have most of their lines in common.
In the dev-side Git repository where you develop your app’s code, create a Git remote pointing to the serverside location where you’ll push production code. (You’ll need to have your Laravel project (on the dev machine) in a Git repo for this workflow to work; take care of this now if you haven’t already.)
# Change into your Laravel project Git repo on your dev machine
you@dev$ cd /path/to/your/laravel-git-repo
# Create a Git remote, called "prod", linked to your app's server
you@dev$ git remote add prod ssh://laravel@laravel_git:/home/laravel/repo/laravel.git
Here’s a breakdown of git remote
command:
git remote add
creates new Git remotes.
prod
is the remote name. The name is your choice; I chose prod
because we’ll use this remote for production code.
ssh
is the protocol used to connect to the server. This should stay as is.
laravel
is the name of the serverside user you will create (in a few articles) to administer your web app.
laravel_git
is the SSH Host created above for pushing code to your production server.
It must exactly match the Host
field used in the previous section.
/home/laravel/repo/laravel.git
is the path, on the server, to the serverside Git repo storing your web app.
You can use git remote set-url
anytime you need to update or edit the prod
remote’s URL on your development machine:
# Update the URL used for the production remote
you@dev$ git remote set-url prod ssh://laravel@laravel_git:/home/laravel/repo/laravel.git
From your development machine, push your app’s code to the production remote you set up in the previous step
(I’m assuming you’re pushing the main
branch, adjust if needed).
# Change into your dev-side Git repo
you@dev$ cd path/to/my/laravel-project
# Push your app's `main` branch to the `prod` remote, i.e. to the production server.
# (Assuming you're using a main branch; update branch name (to e.g. master) as necessary.)
you@dev$ git push prod main
The name of the branch in your serverside Git repo must match the name of the branch you’re pushing from your development machine, or the checkout part of the post-receive
hook will fail
(e.g. both server and dev branches should be main
, or both should be master
, etc.).
You can check the current branch name in both the serverside and dev-side repos with git branch
.
The most likely way you’d run into problems is having a master
branch on your server (by default most Git distributions will use master
as the default name) and a main
branch on your dev machine.
You could solve this, for example, by renaming the serverside branch to main
, or, more permanently, set main
as the default serverside branch name with git config --global init.defaultBranch main
and create a new serverside Git repo, which will then have main
as the default branch.
Here’s what should happen:
Git on your dev machine reads the laravel_git
SSH host information you added in the previous step, recognizes which SSH key to use to connect to the server (you might be prompted for the SSH key’s password, or ssh-agent
might take of this for you under the hood, depending on your SSH setup).
Your app is pushed to the serverside Git repo (SSH into the server and check the contents of git log
in /home/laravel/repo/laravel.git
).
(Note that, assuming you’re following along with the tutorial and using a bare serverside Git repo, your app’s files will not appear on the file system in the serverside Git repo because a bare Git repo does not have a working tree. That is expected—you confirm the push’s success with git log
, which should show your project’s commit history.)
Errors with Git’s SSH connection to the server are probably due to an SSH or Git misconfiguration on your dev machine.
Double check that the alias in ~/.ssh/config
and the Git remote URL match on your dev machine what’s in this article.
Serverside errors, at least at this stage, most likely arise from Git branch naming conflicts—see the warning about matching Git branch names above.
Give this and the previous two dev-side steps a reread just be sure, and then please let me know if you’re still having problems pushing code to the server—it might be a mistake in this guide.
Check-in point: check that the output of git log
in your serverside Git repo (/home/laravel/repo/laravel.git
) shows your project’s commit history.
We’ll now copy code from the serverside Git repo to the directory from which you’ll serve your app.
SSH into your server, and create a directory to hold your app’s initial release:
# Change into your web server directory
laravel@server$ cd /srv/www/laravel/
# Create a directory to hold your initial release
laravel@server$ mkdir -p releases/initial
Then change into the serverside Git repo and use git checkout
to copy your app’s code into the initial release directory.
# Change into your serverside Git repo
laravel@server$ cd ~/repo/laravel.git
# Using `git checkout`, copy your app's code into the initial release directory
laravel@server:laravel.git$ git --work-tree=/srv/www/laravel/releases/initial checkout --force
Your app is almost ready to go live at this point.
Check-in point: at this stage, you should see your app’s files in the initial release directory /srv/www/laravel/releases/initial/
.
Your app’s .env
file and storage
directory (and *.sqlite
database file, if using SQLite) are kept in the /srv/www/laravel/shared/
directory, and are shared between releases.
You should now create symlinks linking these shared files into place in your initial
release:
SHARED=/srv/www/laravel/shared/
RELEASE=/srv/www/laravel/releases/initial/
# Link shared env file into place
laravel@server$ ln -s ${SHARED}/.env ${RELEASE}/.env
# Replace release's boilerplate storage directory with shared storage directory
laravel@server$ rm -rf ${RELEASE}/storage
laravel@server$ ln -s ${SHARED}/storage ${RELEASE}/storage
# If your app uses SQLite, create a parent directory and link database into place
laravel@server$ mkdir -p ${RELEASE}/database/sqlite
laravel@server$ ln -s ${SHARED}/sqlite/database.sqlite ${RELEASE}/database/sqlite/database.sqlite
You’re now ready to run a few standard Laravel deployment commands (most of which you can find in the Laravel docs).
Begin by installing your app’s PHP dependencies using Composer:
# Change into the intial release directory
laravel@server$ cd /srv/www/laravel/releases/initial/
# Install your app's PHP dependencies
laravel@server$ composer install --no-dev --optimize-autoloader
Then install and build your app’s JavaScript dependencies (skip this step if your app has no JavaScript dependencies):
# Install your app's JavaScript dependencies
laravel@server$ npm install
# You may want to also audit your JavaScript dependencies for vulnerabilities
laravel@server$ npm audit fix
# Build your app's JavaScript dependencies
laravel@server$ npm run build
Then cache your config settings, routes, and Blade views:
# Clear any stale cached settings...
laravel@server$ php artisan config:clear
laravel@server$ php artisan route:clear
laravel@server$ php artisan view:clear
# ...then recache config, route, and Blade views
laravel@server$ php artisan config:cache
laravel@server$ php artisan route:cache
laravel@server$ php artisan view:cache
You should then run your database migrations. And if you are seeding your database, now is a good time to do it:
# Run database migrations
laravel@server$ php artisan migrate
# Seed database, if part of your workflow
laravel@server$ php artisan db:seed
Now that you have installed your app’s PHP packages and thus have access to artisan
, you should generate your app’s encryption key.
First, open your app’s .env
file and ensure it contains a blank APP_KEY=
field, which will be populated with your app’s encryption key.
Then generate the key:
# Change into the intial release directory
laravel@server$ cd /srv/www/laravel/releases/initial/
# Generate your app's encryption key
laravel@server$ php artisan key:generate
The key should appear in the APP_KEY
field of your app’s .env
file.
To quickly review from the permissions and ownership article, your app’s bootstrap/cache
, storage
and sqlite
directory must be writable by the web server process that serves your app.
We’ll address this by giving the Nginx user www-data
group ownership of your release files, and group write permissions on these special directories.
# Grant group ownership of your app's files to www-data
laravel@server$ sudo chgrp -R www-data /srv/www/laravel/
# Grant owning group write access special directories
laravel@server$ sudo chmod -R g=rwX /srv/www/laravel/releases/initial/bootstrap/cache
laravel@server$ sudo chmod -R g=rwX /srv/www/laravel/shared/storage
laravel@server$ sudo chmod -R g=rwX /srv/www/laravel/shared/sqlite
Activate the release:
RELEASE=/srv/www/laravel/releases/initial
ACTIVE=/srv/www/laravel/active
# Point active release to the intial release directory
laravel@server$ ln -sfn ${RELEASE} ${ACTIVE}
For good measure, restart the Nginx web server with sudo nginx -s reload
.
Your app should then be live when you point a web browser to your app’s domain name or IP address.
The final phase of the tutorial shows how to automate the redeployment process.
Finding this tutorial series useful? Consider saying thank you!
The original writing and media in this series is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.