Deployment with Deployer

Overview

Automized deployments shouldn't be a pain and have several advantages like lower failure rates, reproducible builds, and they increase the overall productivity, because actual testing can get more attention.
This article explains the fundamental steps it takes, to deploy Shopware 6 to a certain infrastructure, focussing on continuous deployment using GitLab CI and Deployer. Deployer is a deployment tool written in PHP.
This "certain infrastructure" will be called "target server" in the following.

Video

Prerequisites

Please make sure you already have a working Shopware 6 instance running, and your repository is based on the Shopware production template, because this article relies on some scripts to exist in your repository.
GitHub - shopware/production: Shopware 6 production template
GitHub

Preparations before the first deployment

​Deployer has a default directory structure, in which it organizes releases, shared files across releases (e.g. certificates, configuration or media files) and the symlink to the current release.
The structure looks like this:
1
β”œβ”€β”€ .dep
2
β”œβ”€β”€ current -> releases/1
3
β”œβ”€β”€ releases
4
β”‚ └── 1
5
└── shared
6
β”œβ”€β”€ .env
7
└── config
8
└── ...
Copied!
If you haven't used such a structure yet, it's recommended to move the current document root contents to a different location, because you will have to copy some existing files into the shared folder after your first deployment with Deployer.
For more information, please have a look into Migrating existing instance to Deployer structure.

Webserver configuration

Please make sure to set the document root of the domain to /var/www/shopware/current/public, assuming /var/www/shopware is the path you're uploading Shopware to, but this can of course differ. The more important part of this path is current, which is the symlink to the currently active release.
Because current is a symlink, please also make sure your webserver is configured to resolve/follow symlinks correctly.

GitLab Runner requirements

​GitLab pipelines are processed by so called runners. Once a pipeline job is created, GitLab notifies a registered runner, and the job will then be processed by that runner.
The GitLab Runner must have the following packages installed:
This example uses the docker image shopware/development:latest. This image meets all requirements.

Deployment steps

1. Cloning the repository

The very first needed step in the pipeline is cloning the repository into the runner's workspace.
GitLab does that automatically for every started job.

2. Installing dependencies

All the dependencies of your project must be installed. Shopware 6 uses Composer for managing PHP dependencies and Node Package Manager (npm) for frontend related dependencies.
Initially only the Composer dependencies need to be installed by running the following commands:
  • $ composer install --no-interaction --optimize-autoloader --no-suggest
  • $ composer install -d vendor/shopware/recovery --no-interaction --optimize-autoloader --no-suggest
This step is defined in the Install dependencies job in the .gitlab-ci.yml:
1
Install dependencies:
2
stage: build
3
image: shopware/development:latest
4
script:
5
- composer install --no-interaction --optimize-autoloader --no-suggest
6
- composer install -d vendor/shopware/recovery --no-interaction --optimize-autoloader --no-suggest
7
cache:
8
key: ${CI_COMMIT_REF_SLUG}
9
paths:
10
- vendor/
11
policy: push
Copied!

3. Building assets

From this step on, all other steps are handled by Deployer, defined in the deploy.php.
In order to compile and copy assets, the Shopware production template provides a script, which is located under bin/build-js.sh. This script installs the NPM dependencies and builds assets that are needed for the administration, storefront and plugins.
It's important to know, that you need a database connection to execute this script, because before actually compiling the assets, the console command bin/console bundle:dump is executed. This command creates the file var/plugins.json, which contains information about the asset paths of all activated plugins.
If you don't want to build the assets on the target server (for performance reasons), you could execute the bundle:command on the target server and download the generated plugins.json into your runner's workspace, before executing bin/build-js.sh.
This step is defined to be executed on the target server in the sw:build job in the deploy.php and will be executed before transferring the files to the target server:
1
task('sw:build', static function () {
2
run('cd {{release_path}} && bash bin/build-js.sh');
3
});
Copied!

4. Transferring the workspace

For transferring the files to the target server, please configure at least one host in the deploy.php:
1
host('SSH-HOSTNAME')
2
->stage('production')
3
->user('SSH-USER')
4
->set('deploy_path', '/var/www/shopware') // This is the path, where deployer will create its directory structure
5
->set('http_user', 'www-data') // Not needed, if the `user` is the same user, the webserver is running with
6
->set('writable_mode', 'chmod');
Copied!
This step is defined in the deploy:update_code job in the deploy.php:
1
task('deploy:update_code', static function () {
2
upload('.', '{{release_path}}');
3
});
Copied!

5. Applying migrations

The migrations need to be applied on the target server.
If you are deploying to a cluster with multiple web servers, please make sure to run the migrations only on one of the servers.
This step is defined in the sw:database:migrate job in the deploy.php, which is part of the sw:deploy task group:
1
task('sw:database:migrate', static function () {
2
run('cd {{release_path}} && bin/console database:migrate --all');
3
});
Copied!

6. Warming up caches

If you have the HTTP cache enabled in your .env file, it's recommended to warm up the caches, so that the first user, who visits the recently deployed version, doesn't have to wait, until the page is rendered for the first time.
This step is defined in the sw:cache:warmup job in the deploy.php:
1
task('sw:cache:warmup', static function () {
2
run('cd {{release_path}} && bin/console cache:warmup');
3
run('cd {{release_path}} && bin/console http:cache:warm:up');
4
});
Copied!

7. Creating the install.lock file

Before putting the new version live, please make sure to create an empty file install.lock in the root of the build workspace. Otherwise Shopware will redirect every request to the Shopware installer, because it assumes, that Shopware isn't installed yet.
This task is defined in the sw:touch_install_lock job in the deploy.php, which is part of the sw:deploy task group:
1
task('sw:touch_install_lock', static function () {
2
run('cd {{release_path}} && touch install.lock');
3
});
Copied!

8. Switching the document root

After all steps are done, Deployer will switch the symlinks destination to the new release.
This task is defined in the deploy:symlink default job in the deploy.php.

Deployer output

This is the output of dep deploy production:
1
$ dep deploy production
2
​
3
βœ” Executing task deploy:prepare
4
βœ” Executing task deploy:lock
5
βœ” Executing task deploy:release
6
βœ” Executing task deploy:update_code
7
βœ” Executing task deploy:shared
8
βœ” Executing task sw:touch_install_lock
9
βœ” Executing task sw:build
10
βœ” Executing task sw:database:migrate
11
βœ” Executing task sw:theme:compile
12
βœ” Executing task sw:cache:clear
13
βœ” Executing task deploy:writable
14
βœ” Executing task deploy:clear_paths
15
βœ” Executing task sw:cache:warmup
16
βœ” Executing task deploy:symlink
17
βœ” Executing task deploy:unlock
18
βœ” Executing task cleanup
19
Successfully deployed!
Copied!

Migrating existing instance to Deployer structure

After the very first deployment with Deployer, you have to copy some files and directories from your existing Shopware instance into the directory structure, that was created by Deployer.
Lets agree on the following two paths for the examples:
  1. 1.
    You have copied your existing Shopware instance to /var/www/shopware_backup.
  2. 2.
    You have set the deploy_path in the deploy.php to /var/www/shopware.
Now have a look at the shared_files and shared_dirs configuration in the deploy.php. Simply copy all the paths into /var/www/shopware/shared. For the configuration of the deploy.php the commands would be the following:
1
cp /var/www/shopware_backup/.env /var/www/shopware/shared/
2
cp -R /var/www/shopware_backup/custom/plugins /var/www/shopware/shared/custom
3
cp -R /var/www/shopware_backup/config/jwt /var/www/shopware/shared/config
4
cp -R /var/www/shopware_backup/config/packages /var/www/shopware/shared/config
5
cp -R /var/www/shopware_backup/files /var/www/shopware/shared
6
cp -R /var/www/shopware_backup/var/log /var/www/shopware/shared/var
7
cp -R /var/www/shopware_backup/public/media /var/www/shopware/shared/public
8
cp -R /var/www/shopware_backup/public/thumbnail /var/www/shopware/shared/public
9
cp -R /var/www/shopware_backup/public/sitemap /var/www/shopware/shared/public
Copied!

Sources

Please have a closer look into the following files. All steps are provided with helpful comments.

.gitlab-ci.yml

1
# This file defines the GitLab CI/CD pipeline.
2
# For more information, please visit the GitLab CI/CD docs: https://docs.gitlab.com/ee/ci/README.html
3
variables:
4
GIT_STRATEGY: clone
5
​
6
# Stages define _when_ to run the jobs. For example, stages that run tests after stages that compile the code.
7
# If _all_ jobs in a stage succeed, the pipeline moves on to the next stage.
8
# If _any_ job in a stage fails, the next stage is not (usually) executed and the pipeline ends early.
9
stages:
10
- build
11
- deploy
12
​
13
# This variable holds all commands that are needed to be able to connect to the target server via SSH.
14
# For this you need to define two variables in the GitLab CI/CD variables:
15
# - SSH_PRIVATE_KEY: The contents of the SSH private key file. The public key must be authorized on the target server.
16
# - DEPLOYMENT_SERVER: Just the hostname of the target server (e.g. shopware.com, don't include schema or paths)
17
.configureSSHAgent: &configureSSHAgent |-
18
eval $(ssh-agent -s)
19
echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
20
mkdir -p ~/.ssh
21
ssh-keyscan $DEPLOYMENT_SERVER >> ~/.ssh/known_hosts
22
chmod 700 ~/.ssh
23
​
24
Install dependencies:
25
stage: build
26
image: shopware/development:latest
27
script:
28
- composer install --no-interaction --optimize-autoloader --no-suggest
29
- composer install -d vendor/shopware/recovery --no-interaction --optimize-autoloader --no-suggest
30
​
31
# This tells the GitLab Runner to upload (`policy: push`) the `vendor` directory, which was contains all composer
32
# dependencies to GitLab after the job has finished, so that it can be re-used in other jobs.
33
cache:
34
key: ${CI_COMMIT_REF_SLUG}
35
paths:
36
- vendor/
37
policy: push
38
​
39
Deploy:
40
stage: deploy
41
image: shopware/development:latest
42
only:
43
- master
44
before_script:
45
# First we need to execute all commands, that are defined in the `configureSSHAgent` variable.
46
- *configureSSHAgent
47
# In order to use Deployer for our deployment, it needs to be installed globally via composer.
48
- composer global require deployer/deployer
49
script:
50
# This command starts the workflow, that is defined in the `deploy` task in the `deploy.php`.
51
# `production` is the stage, that was defined in the `host` in the `deploy.php`
52
- dep deploy production
53
​
54
# This tells the GitLab Runner to download (`policy: pull`) the `vendor` directory, which was contains all composer
55
# dependencies into the runner's workspace, before the job starts.
56
# The cache entry was created by the `Install dependencies` job.
57
cache:
58
key: ${CI_COMMIT_REF_SLUG}
59
paths:
60
- vendor/
61
policy: pull
Copied!

deploy.php

1
<?php
2
​
3
namespace Deployer;
4
​
5
require_once 'recipe/common.php';
6
​
7
set('application', 'Shopware 6');
8
set('allow_anonymous_stats', false);
9
set('default_timeout', 3600); // Increase the `default_timeout`, if needed, when tasks take longer than the limit.
10
​
11
// For more information, please visit the Deployer docs: https://deployer.org/docs/configuration.html
12
host('SSH-HOSTNAME')
13
->stage('production')
14
->user('SSH-USER')
15
->set('deploy_path', '/var/www/shopware')
16
->set('http_user', 'www-data') // Not needed, if the `user` is the same user, the webserver is running with
17
->set('writable_mode', 'chmod');
18
​
19
// For more information, please visit the Deployer docs: https://deployer.org/docs/configuration.html#shared_files
20
set('shared_files', [
21
'.env',
22
]);
23
​
24
// For more information, please visit the Deployer docs: https://deployer.org/docs/configuration.html#shared_dirs
25
set('shared_dirs', [
26
'custom/plugins',
27
'config/jwt',
28
'files',
29
'var/log',
30
'public/media',
31
'public/thumbnail',
32
'public/sitemap',
33
]);
34
​
35
// For more information, please visit the Deployer docs: https://deployer.org/docs/configuration.html#writable_dirs
36
set('writable_dirs', [
37
'custom/plugins',
38
'config/jwt',
39
'files',
40
'public/bundles',
41
'public/css',
42
'public/fonts',
43
'public/js',
44
'public/media',
45
'public/sitemap',
46
'public/theme',
47
'public/thumbnail',
48
'var',
49
]);
50
​
51
// This task uploads the whole workspace to the target server
52
task('deploy:update_code', static function () {
53
upload('.', '{{release_path}}');
54
});
55
​
56
// This task remotely creates the `install.lock` file on the target server.
57
task('sw:touch_install_lock', static function () {
58
run('cd {{release_path}} && touch install.lock');
59
});
60
​
61
// This task remotely executes the `bin/build-js.sh` script on the target server.
62
task('sw:build', static function () {
63
run('cd {{release_path}} && bash bin/build-js.sh');
64
});
65
​
66
// This task remotely executes the `theme:compile` console command on the target server.
67
task('sw:theme:compile', static function () {
68
run('cd {{release_path}} && bin/console theme:compile');
69
});
70
​
71
// This task remotely executes the `cache:clear` console command on the target server.
72
task('sw:cache:clear', static function () {
73
run('cd {{release_path}} && bin/console cache:clear');
74
});
75
​
76
// This task remotely executes the cache warmup console commands on the target server, so that the first user, who
77
// visits the website, doesn't have to wait for the cache to be built up.
78
task('sw:cache:warmup', static function () {
79
run('cd {{release_path}} && bin/console cache:warmup');
80
run('cd {{release_path}} && bin/console http:cache:warm:up');
81
});
82
​
83
// This task remotely executes the `database:migrate` console command on the target server.
84
task('sw:database:migrate', static function () {
85
run('cd {{release_path}} && bin/console database:migrate --all');
86
});
87
​
88
/**
89
* Grouped SW deploy tasks
90
*/
91
task('sw:deploy', [
92
'sw:touch_install_lock',
93
'sw:build',
94
'sw:database:migrate',
95
'sw:theme:compile',
96
'sw:cache:clear',
97
]);
98
​
99
/**
100
* Main task
101
*/
102
task('deploy', [
103
'deploy:prepare',
104
'deploy:lock',
105
'deploy:release',
106
'deploy:update_code',
107
'deploy:shared',
108
'sw:deploy',
109
'deploy:writable',
110
'deploy:clear_paths',
111
'sw:cache:warmup',
112
'deploy:symlink',
113
'deploy:unlock',
114
'cleanup',
115
'success',
116
])->desc('Deploy your project');
117
​
118
after('deploy:failed', 'deploy:unlock');
Copied!
Last modified 8mo ago