First of all, I do apologise for writing this long post which is against my rules but let's assume that this never happened:)

In this example, we're going to develop our application in DEV (development) server, deploy it from DEPLOY (deployment) server to both STAG (staging) and PROD (production) servers with capistrano. I'm going to show you how it is done but just to keep the example as short as possible, I won't use CI (Continuous Integration) server otherwise the example would be really really long. Having said that, there are already examples in this blog about how you can add Jenkins server to your workflow so if you want, you can read it too.


Things to know


Setup


We will be using vagrant to install most of the services automatically but if you want you can do things manually. Just look into "bootstrap.sh" files.


Flow and stages


The purple section is called "development" stage and the green section is called "deployment" stage. This is what are going to do and an application development life cycle should be similar to this anyway. As I said, we will ignore Jenkins this time.



Servers



Configurations/setup of Development and Jenkins Servers must be very close to each other because Jenkins will test your application by running your applications tests. Configurations and setup of Staging and Production Servers must be very close to each other because technically staging equals to production.


Application environment isolation


We're going to split our symfony application into 4 environments to keep things separate from each other. We will be using dev for development, test for testing, stag for staging and prod for production environments.


Security


User files such as images and docs are uploaded into a directory which is not under apache web root. We'll keep all that outside of web root instead. For the "public" files, we'll create symlinks but for the "private" files, we'll programatically server them in our application.


Deployment


To be able to deploy your application, you have to SSH into "deployment" server as deployer user. This user won't have sudo permissions but will own application folders. Some folders will be shared between releases so when you release your application, the shared files and folder such as cache, sessions, logs, user files (images, docs etc.) won't get lost.


Application


Config files


You must have 4 config.yml files for your environments. The config_dev.yml, config_test.yml and config_prod.yml files are already there for you so just create config_stag.yml file out of config_prod.yml file.


Parameter files


You must have 3 parameters.yml.dist files for your environments. The parameters.yml.dist file is already there for you so just create parameters_staging.yml.dist and parameters_production.yml.dist files out of parameters.yml.dist file. See the differences below.


# parameters.yml.dist

parameters:
public_image_uploads_path: '%kernel.root_dir%/../web/%kernel.environment%/uploads/images'
private_image_uploads_path: '%kernel.root_dir%/Resources/private/uploads/images'
public_image_uploads_assets_path: '%kernel.environment%/uploads/images'

# parameters_staging.yml.dist

parameters:
public_image_uploads_path: '/srv/www-shared/football/uploads/public/images'
private_image_uploads_path: '/srv/www-shared/football/uploads/private/images'
public_image_uploads_assets_path: 'uploads/public/images'

# parameters_production.yml.dist

parameters:
public_image_uploads_path: '/srv/www-shared/football/uploads/public/images'
private_image_uploads_path: '/srv/www-shared/football/uploads/private/images'
public_image_uploads_assets_path: 'uploads/public/images'

Front controllers


You must have 3 app.php files for your environments. The app_dev.php file is already there for you so just create app_test.php and app_stag.php files. Create app_test.php out of app_dev.php and create app_stag_php out of app.php files. See the differences below.


# app_dev.php and app_test.php
...
$kernel = new AppKernel('dev', true); # For app_dev.php
$kernel = new AppKernel('test', true); # For app_test.php
...

# app.php and app_stag.php
...
$kernel = new AppKernel('prod', false); # For app.php
$kernel = new AppKernel('stag', false); # For app_stag.php
...

Upload paths


Create web/dev/uploads/images and app/Resources/private/uploads/images folders.


.gitignore


...
/app/Resources/private/uploads/images/*
!app/Resources/private/uploads/images/.gitkeep
/web/dev/uploads/images/*
!web/dev/uploads/images/.gitkeep
...

.gitattributes


/deploy export-ignore
Capfile export-ignore
Gemfile export-ignore
Gemfile.lock export-ignore

Capistrano files


deploy.rb


You better checkout before and after hook flows listed in Capistrano Flow and Capistrano::Symfony pages.


# deploy/deploy.rb

# Locked capistrano version.
lock "3.8.0"

#---------------------
# APPLICATION
#---------------------
# The name of the application
set :application, "football"

# The path on the remote server where the application will be deployed
# Must have RW permissions by "deployer" user
set :deploy_to, "/srv/www/#{fetch(:application)}"

# The path on the remote server where the temporary files will be stored
# Must have RW permissions by "deployer" user
set :tmp_dir, "#{fetch(:deploy_to)}/tmp/capistrano"

# The application repository
set :repo_url, "git@github.com:Inanzzz/football.git"

# Asks branch to deploy
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

#---------------------
# SYMFONY
#---------------------
# Symfony command environment
set :symfony_env, fetch(:stage).to_s == "production" ? "prod" : "stag"

# Paths
set :vendor_path, "vendor"
set :web_uploads_public_path, "/web/uploads/public"
set :shared_uploads_path, "/srv/www-shared/football/uploads"
set :shared_uploads_public_images_path, "#{fetch(:shared_uploads_path)}/public/images"
set :shared_uploads_private_images_path, "#{fetch(:shared_uploads_path)}/private/images"

# START: Directory create
set :directories_to_create, [
fetch(:shared_uploads_public_images_path),
fetch(:shared_uploads_private_images_path)
]
after "deploy:check:directories", "application:directory:create"
# END: Directory create

# START: Shared folders between releases
set :linked_dirs, [
fetch(:var_path),
fetch(:vendor_path)
]
# END: Shared folders between releases

# START: File permissions
set :permission_method, :acl
set :file_permissions_users, ["www-data"]
set :file_permissions_paths, [fetch(:var_path)]
before "deploy:set_permissions:acl", "application:directory:permission"
# END: File permissions

# START: Override stage dependent files
set :files_to_override, [
[
"#{fetch(:app_config_path)}/parameters_#{fetch(:stage)}.yml.dist",
"#{fetch(:app_config_path)}/parameters.yml.dist"
]
]
before "composer:run", "application:file:override"
# END: Override stage dependent files

# START: Application necessary commands to run
set :application_commands_to_run, [
"assetic:dump --no-debug --env=#{fetch(:symfony_env)}",
"doctrine:schema:update --force --no-interaction --no-debug --env=#{fetch(:symfony_env)}"
]
after "composer:run", "application:command:run"
# START: Run application commands

# START: Application necessary symlinks
set :symlink_dirs, [
[
fetch(:shared_uploads_public_images_path),
fetch(:web_uploads_public_path)
]
]
after "deploy:symlink:release", "application:directory:symlink"
# END: Application necessary symlinks

# START: Artifacts to clear
set :artifacts_to_clear, [
"#{fetch(:app_config_path)}/config_test.yml",
"#{fetch(:app_config_path)}/config_dev.yml",
"#{fetch(:app_config_path)}/routing_dev.yml",
"#{fetch(:app_config_path)}/parameters.yml.dist",
"#{fetch(:web_path)}/config.php",
"#{fetch(:web_path)}/bundles",
"#{fetch(:web_path)}/dev"
]
after "deploy:cleanup", "application:artifact:clear"
# END: Artifacts to clear

application.rake


# deploy/tasks/application.rake

namespace :application do

namespace :directory do
desc "Creates non-existent directories"
task :create do
on roles(:app) do
puts "-" * 6
puts "Creates non-existent directories"
directories_to_create = *fetch(:directories_to_create)
directories_to_create.each do |directory|
if !test("[ -d #{directory} ]")
execute :mkdir, "-p", directory
end
end
puts "-" * 6
end
end

desc "Prepares +RW permissions for directories"
task :permission do
on roles(:app) do
puts "-" * 6
puts "Preparing +RW permissions for directories"
directories_to_create = *fetch(:directories_to_create)
directories_to_create.each do |directory|
if !test("[ -d #{directory} ]")
execute :mkdir, "-p", directory
else
if !test("[ \"$(ls -A #{directory})\" ]")
append :file_permissions_paths, directory
end
end
end
puts "-" * 6
end
end

desc "Symlink directories"
task :symlink do
on roles(:app) do
puts "-" * 6
puts "Symlinking directories"
symlink_dirs = *fetch(:symlink_dirs)
symlink_dirs.each do |directory|
source = directory[0]
destination = "#{release_path}#{directory[1]}"
if !test("[ -d #{source} ]")
execute :mkdir, "-p", source
end
if !test("[ -d #{destination} ]")
execute :mkdir, "-p", destination
end
execute :ln, "-s", "#{source} #{destination}"
end
puts "-" * 6
end
end
end

namespace :file do
desc "Overrides stage dependent files"
task :override do
on roles(:app) do
puts "-" * 6
puts "Overriding stage dependent files"
files_to_override = *fetch(:files_to_override)
files_to_override.each do |files|
source = "#{release_path}/#{files[0]}"
destination = "#{release_path}/#{files[1]}"
if test("[ -f #{source} ]")
execute :mv, "#{source} #{destination}"
else
Rake::Task["application:raise:exception"].invoke("Not found: #{source}")
end
end
puts "-" * 6
end
end
end

namespace :command do
desc "Runs application necessary commands"
task :run do
on roles (:app) do
puts "-" * 6
puts "Running application necessary commands"
commands = *fetch(:application_commands_to_run)
commands.each do |command|
symfony_console command
end
puts "-" * 6
end
end
end

namespace :artifact do
desc "Clears artifacts"
task :clear do
on roles(:app) do
puts "-" * 6
puts "Clearing artifacts"
artifacts_to_clear = *fetch(:artifacts_to_clear)
artifacts_to_clear.each do |artifact|
artifact = "#{release_path}/#{artifact}"
if test("[ -f #{artifact} ]")
execute :rm, artifact
elsif test("[ -d #{artifact} ]")
execute :rm, "-rf", artifact
else
Rake::Task["symfony:raise:exception"].invoke("Not found: #{artifact}")
end
end
puts "-" * 6
end
end
end

namespace :raise do
desc "Internal method to raise exception"
task :exception, [:message] do |task, args|
on roles(:app) do
raise "\033[0;31m" + args[:message] + "\033[0m"
end
end
end

end

staging.rb


# deploy/stages/staging.rb

# The settings for remote server.
server "192.168.99.40", user: "deployer", roles: %w{app db web}

#---------------------
# SYMFONY
#---------------------
# Artifacts to clear. Opposed to other stage
append :artifacts_to_clear,
"#{fetch(:app_config_path)}/parameters_production.yml.dist",
"#{fetch(:app_config_path)}/config_prod.yml"

#Override stage dependent files
append :files_to_override, [
"#{fetch(:web_path)}/app_stag.php",
"#{fetch(:web_path)}/app.php"
]

production.rb


# deploy/stages/production.rb

# The settings for remote server.
server "192.168.99.50", user: "deployer", roles: %w{app db web}

#---------------------
# SYMFONY
#---------------------
# Artifacts to clear. Opposed to other stage
append :artifacts_to_clear,
"#{fetch(:app_config_path)}/parameters_staging.yml.dist",
"#{fetch(:app_config_path)}/config_stag.yml"

Capfile


set :deploy_config_path, "deploy/deploy.rb"
set :stage_config_path, "deploy/stages"

require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/symfony"
require "capistrano/scm/git"

install_plugin Capistrano::SCM::Git

Dir.glob('deploy/tasks/*.rake').each { |r| import r }

Gemfile


source 'https://rubygems.org'

gem 'capistrano', '~> 3.8'
gem 'capistrano-symfony', '~> 1.0.0.rc1'

Setup development environment (DEV - 192.168.99.10)


Vagrantfile


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
# Box type
config.vm.box = "ubuntu/trusty64"
# Sync folder between host and guest machine
# Creates if doesn't exist and grants +RW permissions from host machine
config.vm.synced_folder "www", "/var/www/html", create: true, nfs: true, mount_options: ["actimeo=2"]

# Box settings
config.vm.define :vagrant do |vagrant_config|
# Terminal name
vagrant_config.vm.hostname = "dev"
# IP to access from host machine
vagrant_config.vm.network "private_network", ip: "192.168.99.10"
# Script to run while setting up the box
vagrant_config.vm.provision :shell, path: "bootstrap.sh"
end

# Oracle VM VirtualBox settings
config.vm.provider :virtualbox do |virtualbox_config|
# Box name
virtualbox_config.name = "Development"
# Alloved RAM
virtualbox_config.memory = 2048
# Allowed CPU core
virtualbox_config.cpus = 2
end
end

bootstrap.sh


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ------------------ --\n"
echo -e "-- BEGIN BOOTSTRAPING --\n"
echo -e "-- ------------------ --\n"

# VARIABLES ####################################################################
echo -e "-- GLOBAL VARIABLES\n"
echo -e "-- Setup variables\n"
APP_WEB_ROOT=/var/www/html/football/web
PHP_INI=/etc/php5/apache2/php.ini
APACHE_CONFIG=/etc/apache2/apache2.conf
PHPMYADMIN_CONFIG=/etc/phpmyadmin/config-db.php
SITES_ENABLED=/etc/apache2/sites-enabled
LOCALHOST=127.0.0.1
VHOST=football.${HOSTNAME}
VHOST_PORT=8081
MYSQL_DATABASE=football_${HOSTNAME}
MYSQL_USER=root
MYSQL_PASSWORD=root

# BOX ##########################################################################
echo -e "-- SYSTEM\n"
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# APACHE #######################################################################
echo -e "-- APACHE\n"
echo -e "-- Installing Apache web server\n"
apt-get install -y apache2 > /dev/null 2>&1

echo -e "-- Adding ServerName to Apache config\n"
echo "ServerName ${LOCALHOST}" >> "${APACHE_CONFIG}"

echo -e "-- Allowing Apache override to all\n"
sed -i "s/AllowOverride None/AllowOverride All/g" ${APACHE_CONFIG}

echo -e "-- Creating vhost \n"
cat > ${SITES_ENABLED}/${VHOST}.conf <<EOF
<VirtualHost *:${VHOST_PORT}>
ServerName ${VHOST}
DocumentRoot ${APP_WEB_ROOT}

<Directory ${APP_WEB_ROOT}>
Options Indexes FollowSymlinks
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>

ErrorLog ${APACHE_LOG_DIR}/${VHOST}-error.log
CustomLog ${APACHE_LOG_DIR}/${VHOST}-access.log combined
</VirtualHost>
EOF

echo -e "-- Enabling vhost \n"
a2ensite ${VHOST}.conf> /dev/null 2>&1

echo -e "-- Listening vhost port\n"
echo Listen ${VHOST_PORT} >> /etc/apache2/ports.conf

echo -e "-- Adding vhost to hosts\n"
echo ${LOCALHOST} ${VHOST} >> /etc/hosts

# PHP ##########################################################################
echo -e "-- PHP\n"
echo -e "-- Fetching PHP 5.6 repository\n"
add-apt-repository -y ppa:ondrej/php5-5.6 > /dev/null 2>&1

echo -e "-- Updating packages list\n"
apt-get update -y -qq

echo -e "-- Installing PHP modules\n"
apt-get install -y python-software-properties > /dev/null 2>&1
apt-get install -y libapache2-mod-php5 > /dev/null 2>&1
apt-get install -y libapache2-mod-fastcgi > /dev/null 2>&1
apt-get install -y php5 > /dev/null 2>&1
apt-get install -y php5-cli > /dev/null 2>&1
apt-get install -y php5-mcrypt > /dev/null 2>&1
apt-get install -y php5-curl > /dev/null 2>&1
apt-get install -y php5-dev > /dev/null 2>&1
apt-get install -y php5-fpm > /dev/null 2>&1

echo -e "-- Enabling PHP mcrypt module\n"
php5enmod mcrypt

echo -e "-- Turning PHP error reporting on\n"
sed -i "s/error_reporting = .*/error_reporting = E_ALL/" ${PHP_INI}
sed -i "s/display_errors = .*/display_errors = On/" ${PHP_INI}

# MYSQL ########################################################################
echo -e "-- MYSQL\n"
echo -e "-- Preparing MySQL server installation options\n"
debconf-set-selections <<< "mysql-server mysql-server/root_password password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${MYSQL_PASSWORD}"

echo -e "-- Installing MySQL server and relevant packages\n"
apt-get install -y mysql-server > /dev/null 2>&1
apt-get install -y libapache2-mod-auth-mysql > /dev/null 2>&1
apt-get install -y php5-mysql > /dev/null 2>&1

echo -e "-- Setting up application database\n"
mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -h ${LOCALHOST} -e "CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE}"

# PHPMYADMIN ###################################################################
echo -e "-- PHPMYADMIN\n"
echo -e "-- Preparing phpMyAdmin installation options\n"
debconf-set-selections <<< "phpmyadmin phpmyadmin/dbconfig-install boolean true"
debconf-set-selections <<< "phpmyadmin phpmyadmin/app-password-confirm password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "phpmyadmin phpmyadmin/mysql/admin-pass password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "phpmyadmin phpmyadmin/mysql/app-pass password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2"

echo -e "-- Installing phpMyAdmin\n"
apt-get install -y phpmyadmin > /dev/null 2>&1

echo -e "-- Setting up phpMyAdmin GUI user\n"
sed -i "s/dbuser='phpmyadmin'/dbuser='${MYSQL_USER}'/g" ${PHPMYADMIN_CONFIG}

# SQLITE #######################################################################
echo -e "-- SQLITE\n"
echo -e "-- Installing SQLite and relative modules\n"
apt-get install -y php5-sqlite > /dev/null 2>&1
apt-get install -y sqlite3 > /dev/null 2>&1
apt-get install -y libsqlite3-dev > /dev/null 2>&1

# COMPOSER #####################################################################
echo -e "-- COMPOSER\n"
echo -e "-- Installing PHP cURL module\n"
apt-get install -y curl > /dev/null 2>&1

echo -e "-- Setting up Composer\n"
curl -sSk https://getcomposer.org/installer | php -- --disable-tls > /dev/null 2>&1
mv composer.phar /usr/local/bin/composer

# GIT ##########################################################################
echo -e "-- GIT\n"
echo -e "-- Installing Git\n"
apt-get install -y git > /dev/null 2>&1

# GITHUB #######################################################################
echo -e "-- GITHUB\n"
echo -e "-- Enabling SSH connection to GitHub\n"
mkdir -p ~/.ssh > /dev/null 2>&1
ssh-keyscan -H github.com >> ~/.ssh/known_hosts > /dev/null 2>&1

# REFRESH ######################################################################
echo -e "-- RESTART\n"
echo -e "-- Restarting Apache web server\n"
service apache2 restart

# END ##########################################################################
echo -e "-- ---------------- --"
echo -e "-- END BOOTSTRAPING --"
echo -e "-- ---------------- --"

GitHub SSH integration


$ ssh-keygen -t rsa -b 4096 -C "inanzzz@domain.com"
$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_rsa
$ cat ~/.ssh/id_rsa.pub #Copy this key and add it to GitHub
$ ssh -T git@github.com

Git repository clone


$ git clone git@github.com:Inanzzz/football.git /var/www/html/football

Git config


$ git config --global user.name "Inanzzz"
$ git config --global user.email "inanzzz@domain.com"

Setup staging environment (STAG - 192.168.99.40)


Vagrantfile


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
# Box type
config.vm.box = "ubuntu/trusty64"

# Box settings
config.vm.define :vagrant do |vagrant_config|
# Terminal name
vagrant_config.vm.hostname = "stag"
# IP to access from host machine
vagrant_config.vm.network "private_network", ip: "192.168.99.40"
# Script to run while setting up the box
vagrant_config.vm.provision :shell, path: "bootstrap.sh"
end

# Oracle VM VirtualBox settings
config.vm.provider :virtualbox do |virtualbox_config|
# Box name
virtualbox_config.name = "Staging"
# Sync host and guest machine clocks
virtualbox_config.customize [
"guestproperty",
"set",
:id,
"/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold",
1000
]
end
end

bootstrap.sh


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ------------------ --\n"
echo -e "-- BEGIN BOOTSTRAPING --\n"
echo -e "-- ------------------ --\n"

# VARIABLES ####################################################################
echo -e "-- GLOBAL VARIABLES\n"
echo -e "-- Setup variables\n"
WEB_ROOT=/srv/www
APP_WEB_ROOT=/football/current/web
PHP_INI=/etc/php5/apache2/php.ini
APACHE_CONFIG=/etc/apache2/apache2.conf
LOCALHOST=127.0.0.1
APP_NAME=football
MYSQL_DATABASE=football_${HOSTNAME}
MYSQL_USER=root
MYSQL_PASSWORD=root

# BOX ##########################################################################
echo -e "-- SYSTEM\n"
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# APACHE #######################################################################
echo -e "-- APACHE\n"
echo -e "-- Installing Apache web server\n"
apt-get install -y apache2 > /dev/null 2>&1

echo -e "-- Adding ServerName to Apache config\n"
echo "ServerName ${LOCALHOST}" >> "${APACHE_CONFIG}"

echo -e "-- Creating vhost \n"
cat > /etc/apache2/sites-available/000-default.conf <<EOF
<VirtualHost *:80>
DocumentRoot ${WEB_ROOT}${APP_WEB_ROOT}

<Directory ${WEB_ROOT}${APP_WEB_ROOT}>
AllowOverride All
Require all granted
Allow from All

<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ app.php [QSA,L]
</IfModule>
</Directory>

ErrorLog ${APACHE_LOG_DIR}/${APP_NAME}-error.log
CustomLog ${APACHE_LOG_DIR}/${APP_NAME}-access.log combined
</VirtualHost>
EOF

# PHP ##########################################################################
echo -e "-- PHP\n"
echo -e "-- Fetching PHP 5.6 repository\n"
add-apt-repository -y ppa:ondrej/php5-5.6 > /dev/null 2>&1

echo -e "-- Updating packages list\n"
apt-get update -y -qq

echo -e "-- Installing PHP modules\n"
apt-get install -y python-software-properties > /dev/null 2>&1
apt-get install -y libapache2-mod-php5 > /dev/null 2>&1
apt-get install -y libapache2-mod-fastcgi > /dev/null 2>&1
apt-get install -y php5 > /dev/null 2>&1
apt-get install -y php5-cli > /dev/null 2>&1
apt-get install -y php5-mcrypt > /dev/null 2>&1
apt-get install -y php5-curl > /dev/null 2>&1
apt-get install -y php5-dev > /dev/null 2>&1
apt-get install -y php5-fpm > /dev/null 2>&1

echo -e "-- Enabling PHP mcrypt module\n"
php5enmod mcrypt

echo -e "-- Preventing web directory listing\n"
a2dismod autoindex

echo -e "-- Enabling mod_rewrite module\n"
a2enmod rewrite

# MYSQL ########################################################################
echo -e "-- MYSQL\n"
echo -e "-- Preparing MySQL server installation options\n"
debconf-set-selections <<< "mysql-server mysql-server/root_password password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${MYSQL_PASSWORD}"

echo -e "-- Installing MySQL server and relevant packages\n"
apt-get install -y mysql-server > /dev/null 2>&1
apt-get install -y libapache2-mod-auth-mysql > /dev/null 2>&1
apt-get install -y php5-mysql > /dev/null 2>&1

echo -e "-- Setting up application database\n"
mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -h ${LOCALHOST} -e "CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE}"

# GIT ##########################################################################
echo -e "-- GIT\n"
echo -e "-- Installing Git\n"
apt-get install -y git > /dev/null 2>&1

# COMPOSER #####################################################################
echo -e "-- COMPOSER\n"
echo -e "-- Installing PHP cURL module\n"
apt-get install -y curl > /dev/null 2>&1

echo -e "-- Setting up Composer\n"
curl -sSk https://getcomposer.org/installer | php -- --disable-tls > /dev/null 2>&1
mv composer.phar /usr/local/bin/composer

# REFRESH ######################################################################
echo -e "-- RESTART\n"
echo -e "-- Restarting Apache web server\n"
service apache2 restart

# END ##########################################################################
echo -e "-- ---------------- --"
echo -e "-- END BOOTSTRAPING --"
echo -e "-- ---------------- --"

Apache configuration


vagrant@stag:~$ sudo nano /etc/apache2/apache2.conf

# Replace this
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>

# With this
<Directory /srv/www/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

vagrant@stag:~$ sudo service apache2 restart

Create deployment user


vagrant@stag:~$ sudo su -l root
root@stag:~# adduser deployer

Create project folder


root@stag:~# mkdir /srv/www
root@stag:~# mkdir /srv/www-shared
root@stag:~# chown -R deployer /srv/www
root@stag:~# chown -R deployer /srv/www-shared

GitHub SSH integration


root@stag:~# su -l deployer
deployer@stag:~$ ssh-keygen -t rsa -b 4096 -C "inanzzz@domain.com"
deployer@stag:~$ eval "$(ssh-agent -s)"
deployer@stag:~$ ssh-add ~/.ssh/id_rsa
deployer@stag:~$ cat ~/.ssh/id_rsa.pub # Copy this key and add it to GitHub
deployer@stag:~$ ssh -T git@github.com

Setup production environment (PROD - 192.168.99.50)


Vagrantfile


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
# Box type
config.vm.box = "ubuntu/trusty64"

# Box settings
config.vm.define :vagrant do |vagrant_config|
# Terminal name
vagrant_config.vm.hostname = "prod"
# IP to access from host machine
vagrant_config.vm.network "private_network", ip: "192.168.99.50"
# Script to run while setting up the box
vagrant_config.vm.provision :shell, path: "bootstrap.sh"
end

# Oracle VM VirtualBox settings
config.vm.provider :virtualbox do |virtualbox_config|
# Box name
virtualbox_config.name = "Production"
# Sync host and guest machine clocks
virtualbox_config.customize [
"guestproperty",
"set",
:id,
"/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold",
1000
]
end
end

bootstrap.sh


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ------------------ --\n"
echo -e "-- BEGIN BOOTSTRAPING --\n"
echo -e "-- ------------------ --\n"

# VARIABLES ####################################################################
echo -e "-- GLOBAL VARIABLES\n"
echo -e "-- Setup variables\n"
WEB_ROOT=/srv/www
APP_WEB_ROOT=/football/current/web
PHP_INI=/etc/php5/apache2/php.ini
APACHE_CONFIG=/etc/apache2/apache2.conf
LOCALHOST=127.0.0.1
APP_NAME=football
MYSQL_DATABASE=football_${HOSTNAME}
MYSQL_USER=root
MYSQL_PASSWORD=root

# BOX ##########################################################################
echo -e "-- SYSTEM\n"
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# APACHE #######################################################################
echo -e "-- APACHE\n"
echo -e "-- Installing Apache web server\n"
apt-get install -y apache2 > /dev/null 2>&1

echo -e "-- Adding ServerName to Apache config\n"
echo "ServerName ${LOCALHOST}" >> "${APACHE_CONFIG}"

echo -e "-- Creating vhost \n"
cat > /etc/apache2/sites-available/000-default.conf <<EOF
<VirtualHost *:80>
DocumentRoot ${WEB_ROOT}${APP_WEB_ROOT}

<Directory ${WEB_ROOT}${APP_WEB_ROOT}>
AllowOverride All
Require all granted
Allow from All

<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ app.php [QSA,L]
</IfModule>
</Directory>

ErrorLog ${APACHE_LOG_DIR}/${APP_NAME}-error.log
CustomLog ${APACHE_LOG_DIR}/${APP_NAME}-access.log combined
</VirtualHost>
EOF

# PHP ##########################################################################
echo -e "-- PHP\n"
echo -e "-- Fetching PHP 5.6 repository\n"
add-apt-repository -y ppa:ondrej/php5-5.6 > /dev/null 2>&1

echo -e "-- Updating packages list\n"
apt-get update -y -qq

echo -e "-- Installing PHP modules\n"
apt-get install -y python-software-properties > /dev/null 2>&1
apt-get install -y libapache2-mod-php5 > /dev/null 2>&1
apt-get install -y libapache2-mod-fastcgi > /dev/null 2>&1
apt-get install -y php5 > /dev/null 2>&1
apt-get install -y php5-cli > /dev/null 2>&1
apt-get install -y php5-mcrypt > /dev/null 2>&1
apt-get install -y php5-curl > /dev/null 2>&1
apt-get install -y php5-dev > /dev/null 2>&1
apt-get install -y php5-fpm > /dev/null 2>&1

echo -e "-- Enabling PHP mcrypt module\n"
php5enmod mcrypt

echo -e "-- Preventing web directory listing\n"
a2dismod autoindex

echo -e "-- Enabling mod_rewrite module\n"
a2enmod rewrite

# MYSQL ########################################################################
echo -e "-- MYSQL\n"
echo -e "-- Preparing MySQL server installation options\n"
debconf-set-selections <<< "mysql-server mysql-server/root_password password ${MYSQL_PASSWORD}"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${MYSQL_PASSWORD}"

echo -e "-- Installing MySQL server and relevant packages\n"
apt-get install -y mysql-server > /dev/null 2>&1
apt-get install -y libapache2-mod-auth-mysql > /dev/null 2>&1
apt-get install -y php5-mysql > /dev/null 2>&1

echo -e "-- Setting up application database\n"
mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -h ${LOCALHOST} -e "CREATE DATABASE IF NOT EXISTS ${MYSQL_DATABASE}"

# GIT ##########################################################################
echo -e "-- GIT\n"
echo -e "-- Installing Git\n"
apt-get install -y git > /dev/null 2>&1

# COMPOSER #####################################################################
echo -e "-- COMPOSER\n"
echo -e "-- Installing PHP cURL module\n"
apt-get install -y curl > /dev/null 2>&1

echo -e "-- Setting up Composer\n"
curl -sSk https://getcomposer.org/installer | php -- --disable-tls > /dev/null 2>&1
mv composer.phar /usr/local/bin/composer

# REFRESH ######################################################################
echo -e "-- RESTART\n"
echo -e "-- Restarting Apache web server\n"
service apache2 restart

# END ##########################################################################
echo -e "-- ---------------- --"
echo -e "-- END BOOTSTRAPING --"
echo -e "-- ---------------- --"

Apache configuration


vagrant@prod:~$ sudo nano /etc/apache2/apache2.conf

# Replace this
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>

# With this
<Directory /srv/www/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

vagrant@prod:~$ sudo service apache2 restart

Create deployment user


vagrant@prod:~$ sudo su -l root
root@prod:~# adduser deployer

Create project folder


root@prod:~# mkdir /srv/www
root@prod:~# mkdir /srv/www-shared
root@prod:~# chown -R deployer /srv/www
root@prod:~# chown -R deployer /srv/www-shared

GitHub SSH integration


root@stag:~# su -l deployer
deployer@prod:~$ ssh-keygen -t rsa -b 4096 -C "inanzzz@domain.com"
deployer@prod:~$ eval "$(ssh-agent -s)"
deployer@prod:~$ ssh-add ~/.ssh/id_rsa
deployer@prod:~$ cat ~/.ssh/id_rsa.pub # Copy this key and add it to GitHub
deployer@prod:~$ ssh -T git@github.com

Setup deployment environment (DEPLOY - 192.168.99.30)


Vagrantfile


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
# Box type
config.vm.box = "ubuntu/trusty64"

# Box settings
config.vm.define :vagrant do |vagrant_config|
# Terminal name
vagrant_config.vm.hostname = "deploy"
# IP to access from host machine
vagrant_config.vm.network "private_network", ip: "192.168.99.30"
# Script to run while setting up the box
vagrant_config.vm.provision :shell, path: "bootstrap.sh"
end

# Oracle VM VirtualBox settings
config.vm.provider :virtualbox do |virtualbox_config|
# Box name
virtualbox_config.name = "Deployment"
end
end

bootstrap.sh


#!/usr/bin/env bash

# BEGIN ########################################################################
echo -e "-- ------------------ --\n"
echo -e "-- BEGIN BOOTSTRAPING --\n"
echo -e "-- ------------------ --\n"

# BOX ##########################################################################
echo -e "-- SYSTEM\n"
echo -e "-- Updating packages list\n"
apt-get update -y -qq

# RUBY #########################################################################
echo -e "-- RUBY\n"
echo -e "-- Fetching Ruby repository\n"
add-apt-repository -y ppa:brightbox/ruby-ng > /dev/null 2>&1

echo -e "-- Updating packages list\n"
apt-get update -y -qq

echo -e "-- Installing Ruby\n"
apt-get install -y ruby2.2 > /dev/null 2>&1

# BUNDLER ######################################################################
echo -e "-- BUNDLER\n"
echo -e "-- Installing Ruby gems\n"
gem install bundler > /dev/null 2>&1

# CAPISTRANO ###################################################################
echo -e "-- CAPISTRANO\n"
echo -e "-- Installing Capistrano\n"
gem install capistrano > /dev/null 2>&1
gem install capistrano-ext > /dev/null 2>&1

# GIT ##########################################################################
echo -e "-- GIT\n"
echo -e "-- Installing Git\n"
apt-get install -y git > /dev/null 2>&1

# END ##########################################################################
echo -e "-- ---------------- --"
echo -e "-- END BOOTSTRAPING --"
echo -e "-- ---------------- --"

Create deployment user


vagrant@deploy:~$ sudo su -l root
root@deploy:~# adduser deployer

Create project folder


root@deploy:~# mkdir /projects
root@deploy:~# chown -R deployer /projects

GitHub SSH integration


root@deploy:~# su -l deployer
deployer@deploy:~$ ssh-keygen -t rsa -b 4096 -C "inanzzz@domain.com"
deployer@deploy:~$ eval "$(ssh-agent -s)"
deployer@deploy:~$ ssh-add ~/.ssh/id_rsa
deployer@deploy:~$ cat ~/.ssh/id_rsa.pub # Copy this key and add it to GitHub
deployer@deploy:~$ ssh -T git@github.com

Authorising deployer user


deployer@deploy:~$ cat ~/.ssh/id_rsa.pub | ssh deployer@192.168.99.40 'cat >> /home/deployer/.ssh/authorized_keys'
deployer@deploy:~$ cat ~/.ssh/id_rsa.pub | ssh deployer@192.168.99.50 'cat >> /home/deployer/.ssh/authorized_keys'

Git clone application


deployer@deploy:~$ git clone git@github.com:Inanzzz/football.git /projects/football

Bundler installation


deployer@deploy:/projects/football$ bundle install

Tests


deployer@deploy:/projects/football$ bundle exec cap staging deploy
deployer@deploy:/projects/football$ bundle exec cap production deploy

File and folder structure after deployment.


deployer@prod:~$ ls -l /srv/
drwxr-xr-x 3 deployer root 4096 Apr 26 09:48 www
drwxr-xr-x 3 deployer root 4096 Apr 26 09:49 www-shared

deployer@prod:~$ ls -l /srv/www-shared/football/uploads/public/images/
-rw-r--r-- 1 www-data www-data 627 Apr 26 09:56 open_lock.png
-rw-r--r-- 1 www-data www-data 1167 Apr 26 09:56 open_sign.png

deployer@prod:~$ ls -l /srv/www-shared/football/uploads/private/images/
-rw-r--r-- 1 www-data www-data 627 Apr 26 09:56 closed_lock.png
-rw-r--r-- 1 www-data www-data 1167 Apr 26 09:56 closed_sign.png

deployer@prod:~$ ls -l /srv/www/football/
lrwxrwxrwx 1 deployer deployer 41 Apr 26 09:56 current -> /srv/www/football/releases/20170417235339
drwxrwxr-x 6 deployer deployer 4096 Apr 26 09:56 releases
drwxrwxr-x 7 deployer deployer 4096 Apr 26 09:56 repo
-rw-rw-r-- 1 deployer deployer 216 Apr 26 09:56 revisions.log
drwxrwxr-x 4 deployer deployer 4096 Apr 26 09:49 shared
drwxrwxr-x 3 deployer deployer 4096 Apr 26 09:48 tmp

deployer@prod:~$ ls -l /srv/www/football/shared/
drwxrwxr-x+ 5 deployer deployer 4096 Apr 26 09:56 var
drwxrwxr-x 16 deployer deployer 4096 Apr 26 09:56 vendor

deployer@prod:~$ ls -l /srv/www/football/releases/20170417235339/
drwxrwxr-x 4 deployer deployer 4096 Apr 17 19:35 app
drwxrwxr-x 2 deployer deployer 4096 Apr 26 09:56 bin
-rw-rw-r-- 1 deployer deployer 2518 Apr 17 19:35 composer.json
-rw-rw-r-- 1 deployer deployer 85632 Apr 17 19:35 composer.lock
-rw-rw-r-- 1 deployer deployer 1065 Apr 17 19:35 LICENSE
-rw-rw-r-- 1 deployer deployer 978 Apr 17 19:35 phpunit.xml.dist
-rw-rw-r-- 1 deployer deployer 66 Apr 17 19:35 README.md
-rw-rw-r-- 1 deployer deployer 41 Apr 26 09:56 REVISION
drwxrwxr-x 3 deployer deployer 4096 Apr 17 19:35 src
lrwxrwxrwx 1 deployer deployer 28 Apr 26 09:56 var -> /srv/www/football/shared/var
lrwxrwxrwx 1 deployer deployer 31 Apr 26 09:56 vendor -> /srv/www/football/shared/vendor
drwxrwxr-x 6 deployer deployer 4096 Apr 26 09:56 web

deployer@prod:~$ ls -l /srv/www/football/releases/20170417235339/web/uploads/public/
lrwxrwxrwx 1 deployer deployer 46 Apr 26 09:56 images -> /srv/www-shared/football/uploads/public/images

deployer@prod:~$ ls -l /srv/www/football/releases/20170417235339/web/uploads/public/images/
-rw-r--r-- 1 www-data www-data 627 Apr 26 09:56 open_lock.png
-rw-r--r-- 1 www-data www-data 1167 Apr 26 09:56 open_sign.png