In this article, we'll take a pragmatic look at the Laravel folder structure. At first glance, the folder structure of your Laravel project may appear somewhat overwhelming, especially once you reach the deployment phase. Which paths should be writeable? Which paths are version-specific and which definitely aren't?
Our Automatic Build Configuration feature relieves most of the pain, so if you just want to get your Laravel project up and running as quickly as possible, give the suggested build configuration (on the "create app" screen) a try.
It's always nice to have a deeper understanding of the inner workings of your framework of choice, however, so let's go over the folder structure of a Laravel Project - its anatomy, if you will. In order to get a real-world idea of where various parts of the structure come into play, we'll go through them on the basis of an imaginary HTTP request to a simple Laravel application.
The request lifecycle & the Laravel folder structure
public 📁: This is your application "webroot" - only this folder and its contents are supposed to be directly accessible to the end user, which is why you'll generally configure the webserver to use this path as whatever term is synonymous with webroot in the lingo of your favorite HTTP server (for example, Apache calls this the
DocumentRoot). Besides the PHP entrypoint, this folder will hold all assets that should be directly accessible such as your JS and CSS bundle(s).
When a request hits your server where the path does not correspond to an existing file within the webroot (which is the case for most web document and API requests), it will be addressed directly to the
public/index.php file which is native to Laravel.
The index.php will then look in the vendor 📁 folder to require the Composer autoloader at vendor/autoload.php.
The autoloader is a file automatically generated by Composer during
composer install, and when
require'd it will register all the necessary mappings such that PHP will know where to look (i.e. which file holds the corresponding source code to include) when a particular class is referenced. These mappings include both your own application sources (from the
App namespace, inside the app 📁 folder), as well as all the third-party package sources (such as the laravel core framework).
Everything inside vendor 📁 is, as such, either a dependency or a build artifact, neither of which we'll want to store in our repository (lest we open the door to a whole other dimension of unwanted hassle). This is why
vendor is added to
.gitignore, and generated during build by running
composer install. You'll notice that our Automatic Build Configuration suggestion for this particular command will include some flags, such as
--ignore-platform-reqs. This is basically to let composer know we're in a continuous integration context and the current environment is separate from the server on which the application will actually run.
We have lift-off
Once your application sources, the core framework, and the third party sources are loaded and/or registered, you'll see in
public/index.php the http kernel is instantiated, the request instance is captured, and the request will then be processed by the middleware stack and your route handlers.
Throughout the process of handling the request, the framework might interface with a database, cache, third party APIs, as well as with the filesystem: such as when loading configuration, views or reading and writing to the cache. Let's have a look at some of the paths involved and the appropriate way of dealing with them in terms of deployment.
routes 📁 is pretty much universally the "entrypoint" here -- fundamentally speaking, this folder contains PHP files which effectively serve as mappings from URL/path patterns to their corresponding controller methods or functions with (optionally) some middleware assignments added in.
app 📁 this it the bread and butter of your application. The business logic; the good stuff; the reason we developers can demand a raise whilst casually violating the company dresscode. This is where the route declarations point for controller methods, as well as middleware classes, console commands, database models, event listeners, etcetera.
config 📁, as the name suggests, hold configuration for your application. One way to describe this would be to say this contains all the symbols that would be a crime to hardcode. There'll be plenty of static configuration (meaning the value will be the same across all environments such as your local development instance and the production server), however, some of it will, crucially, depend entirely on the environment. This is where you'll find references to the environment, meaning these values may either be supplied as system environment variables or by populating .env 📄 at the root of your application. We'll return to this file in a bit, but for now it's important to note that the sources in the config folder never contain any secrets such are API keys, are tracked in your git repository. As such, at least the sources themselves are the same across all environments.
Then there's bootstrap 📁. If you're familiar with service providers in Laravel, you can think of bootstrap/app.php as the "service provider to rule them all". This is where the application instance is spawned, and the HTTP and Console kernels of your application are registered with the IoC container. Also, you'll probably never have to touch this file. More notably, there's a 'cache' folder here which helps with the Laravel-specific package resolution and service container performance. The framework will most likely write to this only once, when a new release is first published, so you don't want to share this folder across multiple releases but you do want to make it writeable.
You can think of storage 📁 like a file system where data is written and read at runtime. Besides things like user uploads (which will usually go in
storage/app, the local filesystem disk for laravel), there's
storage/logs for the application log file(s) which can often contain essential information while debugging your app in production,
storage/framework with 'cache' for the filesystem cache (if applicable), 'views' for the compiled views, and 'sessions' for user sessions (given that you're using the filesystem cache driver). You'll want to add a bunch of these to the shared paths (more on that later) to make sure that at the very least your user sessions are preserved across different releases, and you'll need to make sure the server process can write to these locations.
It's not all runtime..
Similar to config 📁, the database 📁 folder contains definitions for how the application should behave or be provisioned - more specifically, the migrations in this folder outline the database structure. Unlike config 📁, however, such migrations are not referenced at runtime but rather from the command line as a part of your integration process.
Obviously you're free to add folders of your own if you feel like it. I personally like to create an "assets" folder at the root where I'll store stuff like the Illustrator source files for
.svg icons so that I have everything in one place.
Let's deploy your Laravel project!
Of course we didn't forget about our call-to-action! We hope the info above helps illuminate some of the inner workings of the Laravel framework. We personally started out building Laravel applications, and Launchdeck began as nothing more than a "scratching-our-own-itch" tool. Nowadays a significant percentage of Launchdeck users are deploying Laravel apps on the daily and we're always looking to improve the experience there. Let's wrap up the anatomy lesson and look at how it ties into the deployment process:
|app 📁||Core code of the application|
|bootstrap 📁||The app.php file which will bootstrap the framework for each request in a regular project. Inside the cache directory here, Laravel will generate files for performance optimisation (not tracked in version control)|
|config 📁||Application configuration files|
|database 📁||Database migrations, factories (if applicable), and seeders|
|public📁||The entrypoint index.php and all externally accessible files (usually asset bundles generated during the build process)|
|routes 📁||Route definitions|
|storage 📁||Logs, generated files and caches, sessions (if using the filesystem cache driver) and, if applicable, user-generated files that should be publicly accessible|
|tests 📁||Automated tests - you may run these during the build as an additional "sanity check" but shouldn't need them on your server|
|vendor 📁 (not in vesion control)||Composer dependencies, typically generated during the build phase|
As we've outlined in other articles, in order for your application to work properly once on your server, it'll reference various files that shouldn't be tracked in version control. As you don't want your production server to be responsible for generating those files either, this is where the build phase comes in. Launchdeck can run arbitrary commands in an isolated container, letting you install dependencies, transpile assets, maybe even run tests.
A great way to get a head start here is to use our automatic build configuration feature, which will analyse the contents of your repository and suggest a build configuration appropriate for your project. This is also a perfect way to familiarise yourself with the way the build configuration manifest is structured. Let's have a look at an example configuration for a typical Laravel project:
build: - cmd: composer install --ignore-platform-reqs --no-progress - cmd: npm install - cmd: npm run build purge: - tests - node_modules - resources/assets - storage/framework/sessions - storage/app - storage/logs shared: - storage/framework/sessions/ - storage/app/ - storage/logs/
I hope you'll agree that once you're familiar with the structure of a Laravel project, a lot of this makes sense straight away. As you can see, the first thing we do is install the composer dependencies which generates the contents of the vendor folder.
The next two commands apply if the front-end resources for your application live in the same repository. If this repository is "only" an API ("headless"), you can skip both these steps. As the commands themselves suggest,
npm install installs the npm dependencies necessary for your build. This will generate the node_modules folder and its contents, often constituting thousands or tens of thousands of files. You'll need them during the next command, but you really don't want them part of your release as it'll slow down the transfer to your server significantly - hence this path is also listed under "purge".
npm run build command runs the "build" script in your package.json manifest. Feel free to swap
yarn in both of these commands!
Then there's the "purge" paths. This is simply a list of globs that will be wiped after all the build steps are completed, but before the resulting set of files (which we call a release) is shipped off to your server. It's very important to make sure that files or directories you don't need on your server are listed here.
Lastly, in the build config, there's the "shared" paths. These are the directories (denoted with a trailing slash) or files that are not part of the release but rather should persist when you deploy a new version of your application. This is where you should list stuff like the user-generated uploads directory, sessions, logs, etc. You may also choose to include ".env" in this list, although we offer the config files feature specifically for this purpose.
Finally, there's SSH commands. If you're using an SSH target server (which we would highly recommend for a variety of reasons), you can set up arbitrary commands to be executed on your server during various phases of the deployment process. For now, let's have a look at the significance of such commands in a context of a Laravel application.
If you've ever added new migrations to your application, you'll know that timing is important in order to prevent downtime or errors. The database should be migrated to be compatible with the latest version of your application, but you don't want your application to be serving requests while the database is still being migrated. This is why we recommend the following commands as "After publish" commands:
php artisan down php artisan migrate --force php artisan up
This ensures that incoming requests won't actually hit the application logic during the migration process. The
--force flag lets the framework know that yes, you do really want to run the migrations in a non-interactive production environment - without it, the command would abort.
Don't wait, deploy!
That about wraps up this anatomy lesson. We'd love to have you aboard, so why don't you give our free tier a try? With Launchdeck you can iterate quickly and effortlessly, and soon you won't even have to think about deployment. We're certain you'll love it!