Deric Cain

Tech ramblings of a true #geek.


Deploying Laravel 5 Apps with Rocketeer

January 2nd, 2016


I wanted a way to deploy my Laravel 5 projects with Rocketeer because it has worked so well for me in the past. I love the fact that it clones a git repo on the staging or production server, so chances are, your code has been tested and verified. Also, if there is an issue, it is very easy to rollback changes to a previous version. With that said, if you have tried deploying Laravel with Rocketeer, there are some hurdles that need jumping and I will explain those and tell you my solution. Here is my setup… * I am using Homestead for local development (which I love, by the way!).* I have a staging environment and a production environment, one Digital Ocean and the other Linode, respectively. * I am using the .env file to set my environmental variables. I have one locally, one on my staging server and one on my production server.

TLDR;

Check out the Gist here.

Hurdle #1 - Multiple Connections (Servers)

I want to use multiple connections with Rocketeer. That was easy in my previous project using Codeigniter, but with Laravel, I had to change some things due to some challenges. I tried to install Rocketeer using Composer, inside of Laravel, but this was a no go. Composer was looking for a dependency that my version of Laravel no longer used and I could not find a way around this. I tried to split my connections up like I outlined here, but that didn’t work either and I believe this was due to namespace issues with Rocketeer. After reading the documentation (who does that???), I found that I could do everything I needed to do in my config.html'connections' => [ 'production' => [ 'host' => 'myapp.com', 'username' => 'user', 'password' => '', 'key' => '', 'keyphrase' => '', 'agent' => '', 'db_role' => true, ], 'staging' => [ 'host' => 'staging.myapp.com', 'username' => 'user', 'password' => '', 'key' => '', 'keyphrase' => '', 'agent' => '', 'db_role' => true, ], ],

That should be fairly self-explanatory, but just in case it’s not, you can read more on connections here. The next thing I needed to do was to tell Rocketeer what my default connection would be. For me, staging would be the default because that is where I will push my changes to test them before they go to production. All you need to do is specify the name of one of your connections as the default like so: 'default' => ['staging'], The reason for setting the default is so that when I type rocketeer deploy, it will use the connection specified as the default. As a side note, when I push to my production server, I simply type rocketeer deploy --on="production" and it will push to my production.

Hurdle #2 - Directories

Since I am using two different servers with different configurations (please don’t yell at me… I know the environments should match for testing purposes. I have my reasons…), I needed to specify which directories these projects would reside when I deploy them, i.e., the web root of the site. By default, Rocketeer is going to try and put the project files in a directory with the same name as your app name. This won’t work for me. In the config.html'on' => [ 'connections' => [ 'staging' => [ 'remote' => [ 'root_directory' => '/srv/users/serverpilot/apps', 'app_directory' => 'myapp', ], ], 'production' => [ 'remote' => [ 'root_directory' => '/var/www', 'app_directory' => 'myapp.com', ], ], ], ],

Within the on key, we just add a connections key and then we can specify our different connections and how we handle our paths (root_directory) and then finally, our directory (app_directory). NOTE: The root_directory needs to be an absolute path and not a relative path.

Hurdle #3 - Multiple Git Branches

With my workflow, I code, push to a dev branch, deploy to staging and then test it out. If that looks good, I then push to master and deploy on production. I needed a way to do this with Rocketeer. In the same on key, within the appropriate connection, I added an scm key with the proper branch name from my Git repo.

'on' => [
    'connections' => [
        'staging' => [
            'remote' => [
                'root_directory' => '/srv/users/serverpilot/apps',
                'app_directory'  => 'myapp',
            ],
            'scm' => [
                'branch' => 'dev' // Look here :)
            ],
        ],
        'production' => [
            'remote' => [
                'root_directory' => '/var/www',
                'app_directory'  => 'myapp.com',
            ],
            'scm' => [
                'branch' => 'master' // And here...
            ],
        ],
    ],
],

Now when I deploy to the respective servers, the correct branch is cloned.

Hurdle #4 - Environment Variables (.env)

This was the one that was the trickiest, yet the solution is very trivial. I love using the .env file to set my environment variables. This way, my config doesn’t get committed to my repo. This is a HUGE security benefit. We don’t want our database credentials on Github… Nuff’ said… When I deploy from Rocketeer, it will clone my git repo in my project directory in a folder named releases. Once it clones the project, it creates a symlink named current points that to the latest release in the releases folder. (This is how you can rollback so easily. It just creates a symlink to a previous release and you’re in business.) Now, I cannot put the .env file in the releases folder, within the latest project release, because this would change each time I deploy. So, I created an .env file on each server with the correct settings and I placed it in the ‘app_directory’ (from the above code), which is not visible to the public. Then, I added a hook in Rocketeer so that after the deployment was complete, it created a symlink in the current release that points to the .env file. Works like a charm! (Check out the hooks key below.)

'on' => [
    'connections' => [
        'staging' => [
            'remote' => [
                'root_directory' => '/srv/users/serverpilot/apps',
                'app_directory'  => 'myapp',
            ],
            'scm' => [
                'branch' => 'dev'
            ],
            'hooks' => [
                'before' => [
                    'setup'   => [],
                    'deploy'  => [],
                    'cleanup' => [],
                ],
                'after'  => [
                    'setup'   => [],
                    'deploy'  => [
                        'ln -s /srv/users/serverpilot/apps/myapp/.env /srv/users/serverpilot/apps/myapp/current/.env ',
                        'chown -R serverpilot:serverpilot /srv/users/serverpilot/apps/myapp',
                    ],
                    'cleanup' => [],
                ],
            ],
        ],
        'production' => [
            'remote' => [
                'root_directory' => '/var/www',
                'app_directory'  => 'myapp.com',
            ],
            'scm' => [
                'branch' => 'master'
            ],
            'hooks' => [
                'before' => [
                    'setup'   => [],
                    'deploy'  => [],
                    'cleanup' => [],
                ],
                'after'  => [
                    'setup'   => [],
                    'deploy'  => [
                        'ln -s /var/www/myapp.com/.env /var/www/myapp.com/current/.env ',
                    ],
                    'cleanup' => [],
                ],
            ],
        ],
    ],
],

If you notice, you can create as many hooks as you would like. I needed to create a hook that would chown the web root on my staging server because permissions were changing after each git clone and the site would not display. That’s it! Laravel 5 and Rocketeer working in perfect harmony. This setup is very easy and it has tremendous advantages to the old school fpt’ing that I’m sure we all did at one point. Here is the whole config.html'application_name' => 'MyApp', 'plugins' => [], 'logs' => function (ConnectionsHandler $connections) { return sprintf('%s-%s-%s.log', $connections->getConnection(), $connections->getStage(), date('Ymd')); }, // The default remote connection(s) to execute tasks on 'default' => ['staging'], 'connections' => [ 'production' => [ 'host' => 'myapp.com', 'username' => 'user', 'password' => '', 'key' => '', 'keyphrase' => '', 'agent' => '', 'db_role' => true, ], 'staging' => [ 'host' => 'staging.myapp.com', 'username' => 'user', 'password' => '', 'key' => '', 'keyphrase' => '', 'agent' => '', 'db_role' => true, ], ], 'use_roles' => false, // Contextual options 'on' => [ 'stages' => [], 'connections' => [ 'staging' => [ 'remote' => [ 'root_directory' => '/srv/users/serverpilot/apps', 'app_directory' => 'myapp', ], 'scm' => [ 'branch' => 'dev' ], 'hooks' => [ 'before' => [ 'setup' => [], 'deploy' => [], 'cleanup' => [], ], 'after' => [ 'setup' => [], 'deploy' => [ 'ln -s /srv/users/serverpilot/apps/myapp/.env /srv/users/serverpilot/apps/myapp/current/.env ', 'chown -R serverpilot:serverpilot /srv/users/serverpilot/apps/myapp', ], 'cleanup' => [], ], ], ], 'production' => [ 'remote' => [ 'root_directory' => '/var/www', 'app_directory' => 'myapp.com', ], 'scm' => [ 'branch' => 'master' ], 'hooks' => [ 'before' => [ 'setup' => [], 'deploy' => [], 'cleanup' => [], ], 'after' => [ 'setup' => [], 'deploy' => [ 'ln -s /var/www/myapp.com/.env /var/www/myapp.com/current/.env ', ], 'cleanup' => [], ], ], ] ], ], ];


blog comments powered by Disqus