Deployments

You can use RockMigrations to create fully automated CI/CD pipelines for Github/Gitlab.

The idea is to replace a workflow that depends on several manual steps with an automated workflow where all you have to do manually is git push.

Folder Structure

The resulting folder structure of a deployment will look like this (where xxx stands for a release hash from github):

current -> release-xxx   // symlink to latest release
release-xxx---           // oldest release
release-xxx--
release-xxx-
release-xxx              // latest release
shared                   // shared folder

The shared folder will contain the persistent data shared across all releases (like site/assets/files and site/config-local.php).

Workflow Log

RockMigrations will create a nice and helpful log on every run:

Github Workflow Log
Github Workflow Log

Setup Variables

This section will list the most important settings of your deployment:

Variables Log

BRANCH: main
PATH: /path/to/your/folder
SSH_USER: youruser
SSH_HOST: yourhost.com
SUBMODULES: true

Deploy via RSYNC

This section will list all files that have been copied from the checked out release to your server.

Trigger RockMigrations Deployment

This section lists the log that is produced by the RockMigrations Deployment from the \RockMigrations\Deployment class which is triggered by the invokation of /site/deploy.php after all files have been copied via rsync.

The log is quite long and verbose so everything should be clear from reading that log.

Setup

Add the /site/deploy.php file

The first thing we need to do is to create the PHP file that is triggered at the end of the rsync:

/site/deploy.php

<?php

namespace RockMigrations;

require_once __DIR__ . "/modules/RockMigrations/classes/Deployment.php";
$deploy = new Deployment($argv ?? []);

// custom settings go here
// see docs about "Customising the Deployment"

$deploy->run();

For the first deployment you can copy and paste this file as it is!

Setup SSH Keys

Github needs to be able to copy files to your remote server. That's why we need to setup SSH keys that we store in the Github Repo's secrets.

Note that we will create an SSH key with the custom name "id_rockmigrations" instead of the default "id_rsa" to ensure that we do not overwrite an existing key.
Don't use the "id_rsa" key for RockMigration Deployments!
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rockmigrations -C "rockmigrations-deployment"

If you are using RockMigrations on multiple projects you can simply overwrite the key on the next deployment setup as you will only need it once during setup. You can also remove that key from your system after you have sucessfully setup your deployment.

Add Secrets to your Repo

Copy the content of the private key to your git secret SSH_KEY:

cat ~/.ssh/id_rockmigrations

Copy the content of keyscan to your git secret KNOWN_HOSTS

ssh-keyscan your.server.com

Add the public key to your remote user (replace user and your.server.com with your custom values):

ssh-copy-id -i ~/.ssh/id_rockmigrations user@your.server.com

Or copy and paste the content of the public key into the ~/.ssh/authorized_keys file of your remote server. To get the content of the public key you can use this command:

cat ~/.ssh/id_rockmigrations.pub

Try to ssh into your server without using a password:

ssh -i ~/.ssh/id_rockmigrations user@your.server.com
The final step must work in order to make the whole deployment work! If the server does not let you connect with the `id_rockmigrations` key then github will not be able to push files to your server!

Optional: Create the test-ssh workflow

If you are new to RockMigrations Deployment I recommend an additional step to check if the SSH connection between Github and your server works. This workflow will not copy any files and will therefore be a lot faster. This makes debugging easier.

.github/workflows/deploy.yaml

name: Deploy via RockMigrations

# run this test-workflow on every push to every branch
on:
  push

jobs:
  test-ssh:
    uses: baumrock/RockMigrations/.github/workflows/test-ssh.yaml@main
    with:
      SSH_USER: youruser
      SSH_HOST: your.server.com
    secrets:
      SSH_KEY: ${{ secrets.SSH_KEY }}
      KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}

Commit the change and push to your repo. You should see the workflow showing up in Github's Actions tab:

Github Actions Tab
Github Actions Tab

Create the final workflow file

Once you got your SSH connection up and running you can setup the deployment.

I always create a workflow file for every branch: main.yaml that fires on pushes to the main branch and dev.yaml that fires when I push to dev:

/.github/workflows/main.yaml

name: Deploy via RockMigrations

on:
  push:
    branches:
      - main

jobs:
  deploy-to-production:
    uses: baumrock/RockMigrations/.github/workflows/deploy.yaml@main
    with:
      PATH: "/path/to/www.yoursite.com"
      SSH_USER: youruser
      SSH_HOST: your.server.com
    secrets:
      SSH_KEY: ${{ secrets.SSH_KEY }}
      KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}

Optional: Using Submodules

If you are using submodules just set the SUBMODULES input variable to true and add a CI_TOKEN to your repo secrets:

/.github/workflows/deploy.yaml

name: Deploy via RockMigrations

on:
  push:
    branches:
      - main

jobs:
  deploy-to-production:
    uses: baumrock/RockMigrations/.github/workflows/deploy.yaml@main
    with:
      PATH: "/path/to/www.yoursite.com"
      SSH_USER: youruser
      SSH_HOST: your.server.com
      SUBMODULES: true
    secrets:
      CI_TOKEN: ${{ secrets.CI_TOKEN }}
      SSH_KEY: ${{ secrets.SSH_KEY }}
      KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}

See here how to setup a Personal Access Token for Github. You need to create this token only once for your Github Account, not for every project. But you need to add it to every project that should be able to access your private submodules!

First Deployment and Cleanup

If everything worked well you should see a success icon in your Github's Actions tab.

You can now remove the SSH keypair id_rockmigrations and id_rockmigrations.pub from your local system if you want. The private key is stored in your Github's Repository Secrets and the public key is stored on the remote server that accepts connections from your Github action.

Setting up the shared folder

RockMigrations will create the shared folder for you on the first deployment, but it will be empty. To make it useful you need to add at least these two things:

  • Add all necessary config settings in /site/config-local.php
  • Upload all files to /site/assets/files

You only need to do this during setup. Once setup it will just work for all following deployments!

Customising the Deployment

share()

The share method tells RockMigrations that the given file or folder should be symlinked from the shared folder. A good example is the /site/assets/files folder that is not part of the Github repository but needs to exist in every release.

share() tells RockMigrations that it should create a symlink to that folder in the shared directory. You need to upload/create that folder or file yourself. Another example is the file /site/config-local.php which is also a shared file used by all releases but never touched on deploy.

delete()

You can tell RockMigrations to delete files or folders after deployment. By default it will remove several folders:

/.ddev
/.git
/.github
/site/assets/cache
/site/assets/ProCache
/site/assets/pwpc-*
/site/assets/sessions

As you can see every deployment will wipe the cache and ProCache folder which will make sure that you don't serve outdated versions of your site!

hooks

You can hook into several places of your deployment. See Deployment.php method run() for all available stages!

This example shows how you can use different .htaccess files for different environments:

/site/deploy.php

<?php

namespace RockMigrations;

require_once __DIR__ . "/modules/RockMigrations/classes/Deployment.php";
$deploy = new Deployment($argv);

$deploy->after("share", function ($deploy) {
  $release = $deploy->paths->release;
  $deploy->exec("rm $release/.htaccess");
  $deploy->exec("mv $release/.htaccess-staging $release/.htaccess");
});

$deploy->run();

Debugging

Debugging can be hard when using CI/CD pipelines. If you get unexpected results during the PHP deployment you can make the script more verbose like this:

/site/deploy.php

...
$deploy->verbose();
$deploy->run();

You can also make it run in dry mode where no files will be copied and only the list of to be executed commands will be shown:

...
$deploy->dry();
$deploy->run();

Or you can dump basic information with TracyDebugger. For example let's say you don't want to delete the sessions folder on every deploy. You can do so like this:

// site/deploy.php
<?php

namespace RockMigrations;

use function ProcessWire\rockmigrations;

require_once __DIR__ . "/modules/RockMigrations/classes/Deployment.php";
$deploy = new Deployment(@$argv);

$deploy->nodelete("/site/assets/sessions");

if (rockmigrations()->isCLI()) $deploy->run();
else bd($deploy);

And then you can place this in /site/ready.php:

if (rockmigrations()->isDDEV()) include "deploy.php";

Which will show debugging info like this:

RockMigrations - Deployments

Create Translations

You can either do that manually or by using RockMigrations:

// install german language pack for the default language
// this will install language support, download the ZIP and install it
$rm->setLanguageTranslations('DE');

In our example that created the language with id 1025 (we will use this id for all following examples.

Then make sure that the content of this folder is added to your GIT repo:

# .gitignore
# exclude all files
/site/assets/files/*
# dont ignore files of given page (eg language files)
!/site/assets/files/1025

Then add those files to your repo and commit - this should look something like this:

RockMigrations - Deployments

Now we just need to push this folder to the shared folder on deployment:

/site/deploy.php

// push german translations to staging/production
$deploy->push('/site/assets/files/1025');

Integrations

VSCode has a "github actions" extension that can help you create workflows or inspect workflow runs:

RockMigrations - Deployments