How to keep one clean Laravel root folder — Deploy local changes to your server with Git

Adriaan De Bolle
Feb 17 · 4 min read

Problem

Some time ago, I was compressing my Laravel folder to upload onto my server to then decompress it, change the environment variables and split the Laravel folder into Laravel resources and public folder.

Local folder structure
|-- htdocs
|-- laravel
Production folder structure
|-- public_html
|-- resources
|-- public

This is AWFUL, needing to split your folder. How could I possibly automate my deployment and use git? I would like this as my folder structure, the same as my local structure:

Production folder structure
|-- public_html
|-- laravel
Keep one clean Laravel folder at serverside is way prettier!

Solution step by step

All following steps cover nearly everything. Most important steps are redirects with .htaccess (step 6) and resolving issues on your server (step 7).

1. Create your git repository

  • Create your repository with your favourite version control tool
    I use Bitbucket where I create the repo “deployment”
  • You should get a command to clone your repo, e.g.
git clone https://unblnd@bitbucket.org/unblnd/deployment.git
  • Use this clone command within your htdocs or where you place all your projects locally within MAMP or WAMP.

2. Create your laravel project

  • cd to your htdocs, create laravel project named “dummy”
laravel new dummy
cd dummy

3. Copy your laravel project into git repo

  • Before copying your laravel project into your git repo you also need to include the hidden .dot files. On Mac you can run
defaults write com.apple.finder AppleShowAllFiles -bool true
killall Finder
  • Instead of killall you can hold alt and right-click finder icon and chose Relaunch
  • Copy paste all your laravel subfolder, files and hidden files into your git repo. Or in this case copy everything within “dummy” and paste into “deployment”
  • You can hide the hidden files again by using the same commands with bool flag false
defaults write com.apple.finder AppleShowAllFiles -bool false
killall Finder

4. Environment variables local vs production

  • Laravel has a default .env.example file. Change this to .env and create an APP_KEY
cp .env.example .env
php artisan key:generate
  • We will make one .env file for local development and one for production to have at all times:
|-- deployment
|-- .env
|-- .env.local
|-- .env.production
  • Basic differences are your url, database name, auth and socket. All other can be customized depending on your needs
  • For example .env.local
APP_URL=http://localhost
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
APP_ENV=local
  • For example .env.production
APP_URL=https://www.unblnd.com/
DB_SOCKET=/var/lib/mysql/mysql.sock
APP_ENV=production

5. Automate deployment with shell script

Before cloning our laravel folder onto our server we need to create another file that we will use to automate deployment at serverside.

  • Create deploy.sh to run at your server to pull changes with git, copy appropriate .env file, reconfigure javascript dependencies and php dependencies. It may be necessary to make your script executable chmod +x deploy.sh
#!/bin/sh# activate maintenance mode
php artisan down
# update and pull local changes
git pull
# change .env file
cp -p .env.production .env
# js deps
rm -rf node_modules && rm -rf yarn.lock
yarn
yarn run prod
# update PHP dependencies
rm -rf vendor && rm -rf composer.lock
composer install --no-interaction --no-dev --prefer-dist
composer update
# update caches
php artisan config:cache
php artisan migrate --force --seed
# --force Required to run when in production.
# stop maintenance mode
php artisan up

6. Move your local changes to your server

  • Commit your changes locally
git add -A
git commit -m "setup"
git push origin master
  • Use SSH to log into your server and clone your laravel repo into public_html. We called this repo “deployment”. First command will save your credentials
git config credential.helper store
git clone https://unblnd@bitbucket.org/unblnd/deployment.git
  • Within public_html of your server you need to add following lines to your .htaccess file to redirect to your public folder
RewriteEngine On
RewriteRule ^(.*)$ deployment/public/$1 [L]

This is the basic configuration. My configuration goes as follows

RewriteEngine on 
RewriteCond %{HTTP_HOST} ^(www.)?unblnd.com$
RewriteCond %{REQUEST_URI} !^/unblnd/public
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /unblnd/public/$1
RewriteCond %{HTTP_HOST} ^(www.)?unblnd.com$
RewriteRule ^(/)?$ unblnd/public/index.php [L]
  • Add following lines to your .htaccess file within deployment/public folder to enable https redirect
RewriteEngine On
RewriteCond %{HTTPS}!=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

This is the basic configuration. My configuration goes as follows, which includes standard laravel rules, compressions, cache control and https redirect

<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
#allows gzip compressions to transfer compressed files to client side
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
#Cache-Control
Header set Cache-Control "max-age=2628000, public"
#image files
<filesMatch ".(jpg|jpeg|png|gif|ico)$">
Header set Cache-Control "max-age=7200, public"
</filesMatch>
#css and js
<filesMatch ".(css|js)$">
Header set Cache-Control "max-age=7200, public"
</filesMatch>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

7. Troubleshooting

  • While moving local folder to your server the folder owners can be incorrect or your permissions or executable issues. You can trace down issues by watching your error_log
tail -f /usr/local/apache/logs/error_log
  • You can check folder owners and group owners with ls -l. Folder owner can be resolved by setting the folder owner to the name of your cpanel user of your top folder (deployment)
chown -R cpaneluser: .
  • It is also possible that permissions need to change of the top folder and public folder
chmod 750 deployment/
chmod 750 deployment/public/
  • Making folder(s) executable depending on your issues can be done as follows
chmod +x deployment/
chmod +x deployment/public/

8. Production: git and deploy

Now we can always just pull our changes git pull or use our shell script to make sure everything is well configured at production level.

./deploy.sh

Adriaan De Bolle

Written by

Creator at UNBLND.com | MSc Engineering

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade