Zero-downtime deployment magento

Set up zero-downtime deployment using magento-actions

In this article will be shown how to make a zero-downtime deployment using our magento-actions. Magento-actions is a github action package that helps magento developers improve their CI/CD processes. Although the action itself does more than handling a zero-downtime deployment, the later will be our main focus here. Fell free to check out the github repo of the actions, to learn more about the other CI/CD operations, enabled by the module, such as testing (integration tests, static tests, etc...), code quality check (phpcs), etc...
The process shown here, can be used to migrate from manual deployment to a zero-downtime one.


Demo in video

1- SSH and server setup

The first thing to do is to create the ssh certificates that will be used by github actions server in order to execute specific operations on the target server (copy tarball, deploy, linking, etc...). We suppose the server used is linux, so we will be using open-ssl 's keychain generator (ssh-keygen) to create a public and private certificates.

For that make sure you have ssh-keygen available, if not consider installing keychain (apt install keychain).

Then execute the following command line to generate said certificates : 

 ssh-keygen -t rsa -b 4096 -N '' -f staging_ssh_key 

Two files will be created : 

  • stagging_ssh_key (the private key)

  • stagging_ssh_key.pub (the public key)

Next, copy the content of the public key file into the file named authorized_keys in the ssh directory on your staging/production server, create the file if none existent.
The file is found in the ~/.ssh/ directory, ~ corresponding to the home directory of the user which will be used to login from github actions server. 
The private key will be feed into github actions secret, we'll come back to it shortly.

Servers and firewall
If your staging and/or production servers are using a firewall, consider whitelisting github actions runners ips.
Unless, you're using a self-hosted runner in which case, make sure nonetheless the ip is whitelisted. 


2- Setup your git project

Now we'll create, and setup on the local host the git repository used a VCS during the devs.
If not already done, execute the following commands : 

 mkdir myAwesomeShop 
 cd myAwesomeShop
 git init .


Now we will set the directory following the required scaffolding:

├── .github       # directory used by github applications i.e github actions
│   └── workflows # directory where the workflows are found, see below for an example of main.yml 
├── README.md 
└── magento # directory where you Magento source files should go


After creating the files and directories, copy the content of your current magento project (root directory) to the magento dir.
And then, create a file name
main.yml in the subdirectory .github/workflows/.
The content of the file will be the following:


For Magento 2.1 to 2.3

     
name: m2-zerodownTime-deploy
on: [push]

jobs:
  magento2-build:
    runs-on: ubuntu-18.04
    container: ubuntu
    name: 'm2 tests & build'
    services:
      mysql:
        image: docker://mysql:5.7
        env:
          MYSQL_ROOT_PASSWORD: magento
          MYSQL_DATABASE: magento
        ports:
          - 3106:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
    steps:
    - uses: actions/checkout@v1
      with:
        submodules: recursive

    - name: 'launch magento2 build'
      if: always()
      uses: MAD-I-T/magento-actions@v2.0
      env:
        COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}}
      with:
        php: '7.1'
        process: 'build'

    - name: 'start magento2 zero downtime deploy'
if: success() uses: MAD-I-T/magento-actions@v2.0 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} BUCKET_COMMIT: bucket-commit-${{github.sha}}.tar.gz MYSQL_ROOT_PASSWORD: magento MYSQL_DATABASE: magento HOST_DEPLOY_PATH: ${{secrets.STAGE_HOST_DEPLOY_PATH}} HOST_DEPLOY_PATH_BUCKET: ${{secrets.STAGE_HOST_DEPLOY_PATH}}/bucket SSH_PRIVATE_KEY: ${{secrets.STAGE_SSH_PRIVATE_KEY}} SSH_CONFIG: ${{secrets.STAGE_SSH_CONFIG}} WRITE_USE_SUDO: false with: php: '7.1' process: 'deploy-staging' - name: 'unlock php deployer if the deployment fails' if: failure() || cancelled() uses: MAD-I-T/magento-actions@v2.0 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} BUCKET_COMMIT: bucket-commit-${{github.sha}}.tar.gz MYSQL_ROOT_PASSWORD: magento MYSQL_DATABASE: magento HOST_DEPLOY_PATH: ${{secrets.STAGE_HOST_DEPLOY_PATH}} HOST_DEPLOY_PATH_BUCKET: ${{secrets.STAGE_HOST_DEPLOY_PATH}}/bucket SSH_PRIVATE_KEY: ${{secrets.STAGE_SSH_PRIVATE_KEY}} SSH_CONFIG: ${{secrets.STAGE_SSH_CONFIG}} WRITE_USE_SUDO: false with: php: '7.1' process: 'cleanup-staging'

For magento 2.4

name: m2-actions-test
on: [push]

jobs:
  magento2-build:
    runs-on: ubuntu-latest
    container: ubuntu
    name: 'm2 unit tests & build'
    services:
      mysql:
        image: docker://mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: magento
          MYSQL_DATABASE: magento
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
      elasticsearch:
        image: docker://elasticsearch:7.1.0
        ports:
          - 9200:9200
        options: -e="discovery.type=single-node" --health-cmd="curl http://localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=10
    steps:
    - uses: actions/checkout@v1
      with:
        submodules: recursive
    - name: 'this step starts static testing the code'
if: always()   uses: MAD-I-T/magento-actions@v3.7 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} with: php: '7.4' process: 'unit-test' elasticsearch: 1 - name: 'this step will build an magento artifact'
uses: MAD-I-T/magento-actions@v3.7 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} with: php: '7.4' process: 'build' elasticsearch: 1 - name: 'starts magento2 zero downtime deploy with no permission check' if: success() uses: MAD-I-T/magento-actions@v3.7 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} BUCKET_COMMIT: bucket-commit-${{github.sha}}.tar.gz MYSQL_ROOT_PASSWORD: magento MYSQL_DATABASE: magento HOST_DEPLOY_PATH: ${{secrets.STAGE_HOST_DEPLOY_PATH}} HOST_DEPLOY_PATH_BUCKET: ${{secrets.STAGE_HOST_DEPLOY_PATH}}/bucket SSH_PRIVATE_KEY: ${{secrets.STAGE_SSH_PRIVATE_KEY}} SSH_CONFIG: ${{secrets.STAGE_SSH_CONFIG}} WRITE_USE_SUDO: false with: php: '7.4' deployer: 'no-permission-check' process: 'deploy-staging' - name: 'unlock php deployer if the deployment fails' if: failure() || cancelled() uses: MAD-I-T/magento-actions@v3.7 env: COMPOSER_AUTH: ${{secrets.COMPOSER_AUTH}} BUCKET_COMMIT: bucket-commit-${{github.sha}}.tar.gz MYSQL_ROOT_PASSWORD: magento MYSQL_DATABASE: magento HOST_DEPLOY_PATH: ${{secrets.STAGE_HOST_DEPLOY_PATH}} HOST_DEPLOY_PATH_BUCKET: ${{secrets.STAGE_HOST_DEPLOY_PATH}}/bucket SSH_PRIVATE_KEY: ${{secrets.STAGE_SSH_PRIVATE_KEY}} SSH_CONFIG: ${{secrets.STAGE_SSH_CONFIG}} WRITE_USE_SUDO: false with: php: '7.4' process: 'cleanup-staging'



Now that the local git project is setup, we shall move on to the github setup.


3- Setup your github repository

Create the github repository to receive your project (instructions on github).
Now that the project is created, time to create the variables used by github actions (e.g  ${{}}  in the file above main.yml).
For that, go to Settings>Secrets of your github project and create and fill the following variables accordingly:



  COMPOSER_AUTH = {"http-basic":{"repo.magento.com": {"username": "xxxxxxxxxxxxxx", "password": "xxxxxxxxxxxxxx"}}}
  STAGE_HOST_DEPLOY_PATH = The path to the root dir on the satging or prod server (i.e /var/www/myawesomeshop/)
  STAGE_SSH_PRIVATE_KEY =  The content of the ssh private key create in the beginning e.g ssh_staging_key 
  STAGE_SSH_CONFIG = 
                    Host staging                # this must be staging or production according to your deployment type
                    User ubuntu                 # the staging/prod username bound to the ssh connecting 
                    IdentityFile ~/.ssh/id_rsa  # Do not modified this unless you know what you're doing
                    HostName my-awesome-shop.fr # the hostname or ip addr of your deployment server prod or staging
                    Port 22                     # The ssh often 22 to be modified accordingly to the server setup



The remaining setup is recommended but optional, indeed, to avoid failure during the first deployment. You should create theses dirs and files:

  1. Create $HOST_DEPLOY_PATH/shared/magento/app/etc/   directory (i.e /var/www/myawsomeshop/shared/magento/app/etc/

  2. Place your configured env.php in the directory

And we set! 

Also when configuring you http server (apache, nginx etc...), the ROOT directory should be 
$HOST_DEPLOY_PATH/current/magento/  .



4- Launch the process

Now you can push your repository to github, the action will automatically start building and deploying your magento to prod (I.e on:[push] in main.yml). Make sure your magento, database and env.php are setup accordingly. Still in any case, if the deployment fails the cleanup task, will be launched.

In a successful scenario, you should see something similar to:

Successful CI/D Magento2


Also we do offer hands on paid support, if you're having trouble setting up your CI/CD, please checkout the offer details here.
Side note: Please feel free to check our Gitlab-CI extension of this tool.