Skip to content

Instantly share code, notes, and snippets.

Last active Jan 22, 2020
What would you like to do?
deploy SilverStripe with PHP-Deployer per git-repo
# ignore everything...
# ...but
# htaccess is ignored since we have a different versions for each stage see in /deploy
# no _ss_environment.php in the repo

deploy SilverStripe with PHP-Deployer per


One-liner deployment like ./vendor/bin/dep deploy and your current Git-repo makes it into a release on your server and creates a DB-dump. You also can ./vendor/bin/dep rollback or have multiple stages whatsoever. It's suits for collaboration, since you deploy a git-repo and the deployment-script comes as dev-dependency per composer.


You need the same PHP Version on HTTP & CLI on both your development-machine and the server you deploy to. Furthermore shell-access to your server is needed and git must be available.


You'll need to install deployer either globally or with composer in the project:

composer require deployer/deployer

The deploy.php recipe assumes, that you have a dev-deploy folder in your project-folder and deploy.php in your project root-folder. dev-deploy must contain an .htaccess and _ss_environment.php, prefixed with the server_name for example live, it should be deployed to. So those files are specific for each server you define. The ones in assets not.

  • live.htaccess (required): The .htaccess file that will be copied to the live server.
  • live_ss_environment.php (required): The _ss_environement.php file that will be copied to the live server.
  • stage.htaccess (required): The .htaccess file that will be copied to the staging server.
  • stage_ss_environment.php (required): The _ss_environement.php file that will be copied to the staging server.
  • ./assets/.htaccess
  • ./assets/web.config


Make sure you can access your server per ssh-key (without passphrase). Use forwardAgent() or add the public key of the server as a "Access keys" on e.g. Bitbucket. In addition the server needs your Git-Repo-Server in the "known_hosts". You can easily achieve this, if you manually do a "git clone ..." on the server and say "yes" when prompted.

Why not official silverstripe recipe

This recipe has some additional tasks and creates a new silverstrpe-cache with each release. ?flush becomes pointless, since it happens anyway with an empty silverstripe-cache folder, so there is not much left compared to "dep init" except /dev/build and that's a one-liner.


These are the additional tasks that are added by the deploy.php recipe. ./vendor/bin/dep deploy uses them all, but you also can for example just create a dump with ./vendor/bin/dep silverstripe:dump

  • silverstripe:createdirs create dumps & log directory with a path like SS_Log::add_writer(new SS_LogFileWriter('../../../log/silverstripe.log'), SS_Log::ERR);
  • silverstripe:dump dump DB into dumps-dir on the server
  • silverstripe:installtools install composer & sspak in ~/bin
  • silverstripe:migrate Run SilverStripe /dev/build
  • silverstripe:prepare uploads .htaccess, web.config to assets
  • silverstripe:setup create silverstripe-cache, upload .htaccess & _ss_environment.php
  • silverstripe:sspak dump DB & assets in a sspak-tarball into dumps-dir on the server


[ ] split up and sanitize tasks to make them usable for different strategies without copy/past


I've modified this on the basis of @bummzack 's, so he did on mine before. If you are looking for a SS-rsync-strategy with gulp integration, you should check (still 3.x ATM):

namespace Deployer;
require 'recipe/common.php';
// Configuration
set('http_user', 'user');
set('repository', '');
set('bin/php', 'php70');
// set('shared_files', []);
set('overwritemycnf', 'false');
set('ssh_type', 'native');
set('ssh_multiplexing', true);
set('shared_dirs', ['assets']);
set('writable_dirs', ['assets', 'silverstripe-cache']);
set('keep_releases', 10);
set('bin/composer', '~/bin/composer.phar');
set('composer_options', 'install --no-dev --verbose --prefer-dist --ignore-platform-reqs --optimize-autoloader --no-interaction');
set('timezone', 'Europe/Zurich');
set('default_stage', 'live');
set('clear_paths', ['dev-deploy', 'deploy.php']);
// Servers
->forwardAgent(true) // You can use identity key, ssh config, or username/password to auth on the server.
->addSshOption('UserKnownHostsFile', '/dev/null')
->addSshOption('StrictHostKeyChecking', 'no')
/// ->identityFile('~/.ssh/', '~/.ssh/id_rsa')
->set('deploy_path', '/home/user/project')
// ->set('branch', 'master')
->set('stage', 'live');
// Tasks
task('silverstripe:readenv', function(){
$stage = get('default_stage');
if (input()->hasArgument('stage') && input()->getArgument('stage') != "") {
$stage = input()->getArgument('stage');
require_once ('dev-deploy/'.$stage.'_ss_environment.php');
set('ss_db_name', SS_DATABASE_NAME);
set('ss_db_host', SS_DATABASE_SERVER);
set('ss_db_pass', SS_DATABASE_PASSWORD);
set('ss_db_user', SS_DATABASE_USERNAME);
// overwrites if stage differs -> or set 'overwritemycnf' to false
task('my_cnf', function() {
$hasMyCnf = run("if [ -e ~/.my.cnf ]; then echo 'true'; fi");
if ('true' != $hasMyCnf || 'true' == get('overwritemycnf')) {
$cmd = <<<CONFIG
cat >~/.my.cnf <<EOL
run(sprintf($cmd, get('ss_db_user'), get('ss_db_pass'), get('ss_db_host')));
} else {
writeln('~/.my.cnf allerdy exists!');
})->desc('create .my.cnf file out of _ss_environment.php');
before('my_cnf', 'silverstripe:readenv');
task('silverstripe:installtools', function() {
$hasSspak = run("if [ -e ~/bin/sspak ]; then echo 'true'; fi");
if ('true' != $hasSspak) {
run('curl -sS | php -- ~/bin');
$hasComposer = run("if [ -e ~/bin/composer.phar ]; then echo 'true'; fi");
if ('true' != $hasComposer) {
// run('wget -P ~/bin');
run('curl --create-dirs -o ~/bin/composer.phar');
run('chmod +x ~/bin/composer.phar');
})->desc('install composer & sspak in ~/bin');
task('silverstripe:prepare', function() {
if(file_exists('dev-deploy/assets/web.config')) {
upload('dev-deploy/assets/web.config', '{{deploy_path}}/shared/assets/web.config');
if(file_exists('dev-deploy/assets/.htaccess')) {
upload('dev-deploy/assets/.htaccess', '{{deploy_path}}/shared/assets/.htaccess');
})->desc('uploads .htaccess, web.config to assets');
task('silverstripe:setup', function() {
writeln('current release path');
$stage = get('default_stage');
if (input()->hasArgument('stage') && input()->getArgument('stage') != "") {
$stage = input()->getArgument('stage');
upload('dev-deploy/{{stage}}_ss_environment.php', '{{deploy_path}}/_ss_environment.php');
// upload files if they locally exist for the current stage
if(file_exists('dev-deploy/{{stage}}_ss_environment.php')) {
upload('dev-deploy/{{stage}}_ss_environment.php', '{{deploy_path}}/_ss_environment.php');
upload('dev-deploy/{{stage}}.htaccess', '{{release_path}}/.htaccess');
// try {{release_path}}
if(file_exists('dev-deploy/{{stage}}.htaccess')) {
upload('dev-deploy/{{stage}}.htaccess', '{{release_path}}/.htaccess');
run('mkdir -p {{release_path}}/silverstripe-cache');
})->desc('create silverstripe-cache, upload .htaccess & _ss_environment.php');
task('silverstripe:dump', function() {
$date = date('Y-m-d--H-i-s');
set('currenttimestamp', $date);
$link = run("readlink {{deploy_path}}/current")->toString();
$current_release = basename($link);
set('current_release', $current_release);
$stage = get('default_stage');
if (input()->hasArgument('stage') && input()->getArgument('stage') != "") {
$stage = input()->getArgument('stage');
try {
run('mysqldump {{ss_db_name}} | gzip -v > {{deploy_path}}/dumps/'.$stage.'--dump--{{currenttimestamp}}--r{{current_release}}.sql.gz');
} catch (Exception $ex) {
$dumpsdirsize = run('du -h {{deploy_path}}/dumps')->toString();
writeln('dumps occupies: ' . $dumpsdirsize);
})->desc('dump DB');
before('silverstripe:dump', 'silverstripe:readenv');
task('silverstripe:sspak', function() {
$date = date('Y-m-d--H-i-s');
set('currenttimestamp', $date);
$link = run("readlink {{deploy_path}}/current")->toString();
$current_release = basename($link);
set('current_release', $current_release);
$stage = get('default_stage');
if (input()->hasArgument('stage') && input()->getArgument('stage') != "") {
$stage = input()->getArgument('stage');
run('~/bin/sspak save {{deploy_path}}/current {{deploy_path}}/dumps/{{stage}}--sspak--{{currenttimestamp}}--r{{current_release}}.tar.gz');
$dumpsdirsize = run('du -h {{deploy_path}}/dumps')->toString();
writeln('dumps occupies: ' . $dumpsdirsize);
})->desc('dump DB & assets in a sspak-tarball');
task('silverstripe:migrate', function() {
run('{{bin/php}} {{release_path}}/framework/cli-script.php dev/build flush=1');
})->desc('Run SilverStripe /dev/build');
task('silverstripe:createdirs', function () {
run("cd {{deploy_path}} && if [ ! -d dumps ]; then mkdir dumps; fi");
run("cd {{deploy_path}} && if [ ! -d log ]; then mkdir log; fi");
})->desc('create dumps & log directory with a path like SS_Log::add_writer(new SS_LogFileWriter(\'../../../log/silverstripe.log\'), SS_Log::ERR);');
// and now say what to do in which order
task('deploy', [
// 'silverstripe:sspak',
// 'deploy:writable',
])->desc('Deploy your project');
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');
<FilesMatch "\.(php|php3|php4|php5|phtml|inc)$">
Deny from all
AddHandler default-handler php phtml php3 php4 php5 inc
<IfModule mod_php5.c>
php_flag engine off
<FilesMatch "\.(php|php3|php4|php5|phtml|inc|txt|scss)$">
Deny from all
AddType font/woff2 woff2
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/html "access plus 5 minutes"
ExpiresByType application/x-font-woff "access plus 7 day"
ExpiresByType application/font-woff "access plus 7 day"
ExpiresByType application/font-woff2 "access plus 7 day"
ExpiresByType font/woff2 "access plus 7 day"
ExpiresByType application/x-font-ttf "access plus 7 day"
ExpiresByType application/font-ttf "access plus 7 day"
ExpiresByType application/x-font-truetype "access plus 7 day"
ExpiresByType application/font-truetype "access plus 7 day"
ExpiresByType application/ "access plus 7 day"
ExpiresByType font/opentype "access plus 7 day"
ExpiresByType application/octet-stream "access plus 7 day"
ExpiresByType image/svg+xml "access plus 7 day"
ExpiresByType image/gif "access plus 7 day"
ExpiresByType image/png "access plus 7 day"
ExpiresByType image/jpg "access plus 7 day"
ExpiresByType image/jpeg "access plus 7 day"
ExpiresByType image/ico "access plus 7 day"
ExpiresByType text/css "access plus 7 day"
ExpiresByType application/javascript "access plus 7 day"
ExpiresByType image/svg+xml "access plus 7 day"
<IfModule mod_headers.c>
Header unset ETag
RequestHeader unset If-Modified-Since
RequestHeader unset If-None-Match
FileETag None
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# Don't compress images, movies or zip files
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:avi|mov|mp3|mp4|rm|flv|swf|mp?g)$ no-gzip dont-vary
<IfModule mod_headers.c>
# properly handle requests coming from behind proxies
Header append Vary User-Agent
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/atom_xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/x-shockwave-flash
AddOutputFilterByType DEFLATE image/svg+xml
AddHandler application/x-httpd-php70 .php
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^deploy.php$ /assets/error-500.html [R=404,L]
# wont expose README
<Files *.md>
Deny from all
# Deny access to templates (but allow from localhost)
<Files *.ss>
Order deny,allow
Deny from all
Allow from
# Deny access to IIS configuration
<Files web.config>
Order deny,allow
Deny from all
# Deny access to YAML configuration files which might include sensitive information
<Files ~ "\.ya?ml$">
Order allow,deny
Deny from all
# Route errors to static pages automatically generated by SilverStripe
ErrorDocument 404 /assets/error-404.html
ErrorDocument 500 /assets/error-500.html
<IfModule mod_env.c>
# Ensure that X-Forwarded-Host is only allowed to determine the request
# hostname for servers ips defined by SS_TRUSTED_PROXY_IPS in your _ss_environment.php
# Note that in a future release this setting will be always on.
SetEnv BlockUntrustedIPs true
<IfModule mod_rewrite.c>
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
<IfModule mod_dir.c>
DirectoryIndex disabled
RewriteEngine On
# Enable HTTP Basic authentication workaround for PHP running in CGI mode
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Deny access to potentially sensitive files and folders
RewriteRule ^vendor(/|$) - [F,L,NC]
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
RewriteRule composer\.(json|lock) - [F,L,NC]
# Process through SilverStripe if no file with the requested name exists.
# Pass through the original path as a query parameter, and retain the existing parameters.
RewriteCond %{REQUEST_URI} ^(.*)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* framework/main.php?url=%1 [QSA]
# If framework isn't in a subdirectory, rewrite to installer
RewriteCond %{REQUEST_URI} ^(.*)/framework/main.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . %1/install.php? [R,L]
/* What kind of environment is this: development, test, or live (ie, production)? */
define('SS_ENVIRONMENT_TYPE', 'live');
/* Database connection */define('SS_DATABASE_SERVER', '');
define('SS_DATABASE_NAME', '');
define('SS_DATABASE_CLASS', 'MySQLPDODatabase');
$_FILE_TO_URL_MAPPING[realpath('/home/user/project/current')] = 'https://project.tld';
/* Configure a default username and password to access the CMS on all sites in this environment. */
ini_set('display_errors', 0);
if (defined('SS_ENVIRONMENT_TYPE') && SS_ENVIRONMENT_TYPE != 'live') {
// turn on display_errors if we are in dev
// NOTE: no need for setting error_reporting, this is done by SilverStripe
ini_set('display_errors', 1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment