Deploy your Drupal website from Gitlab

Learn how to set up a basic deployment script to deploy your Drupal 11 website from Gitlab to your server.

Gitlab

Gitlab

When it comes to storing my code in repositories Gitlab is my partner of choice. The platform works really well and the Drupal Association uses GitLab as its primary collaboration and DevOps platform, hosting all Drupal projects on its own self-hosted GitLab instance and now enabling GitLab CI for all projects on Drupal.org.

The role

The role of our deployment script is that it safely updates your Drupal site, protects your data with backups, and keeps downtime minimal. This should all happen automatically.

Setting this up involves four parts:

  • Deploy script
  • Gitlab CI config file
  • Gitlab variables
  • Deploying our website from Gitlab

In this post I assume your server has access to your Gitlab repository. You can learn how to set this up in this post (part: Access GIT repo from server).

The deployment script

The deployment script below is a simple implementation that has the following steps:

  1. Pulls the latest code from Git.
  2. Installs required dependencies with Composer.
  3. For each site:
    • Turns maintenance mode ON (so visitors don’t see errors).
    • Backs up the database.
    • Runs Drupal updates (database + config changes).
    • Turns maintenance mode OFF.
    • Cleans old backups, keeping only the latest 5.

In this particular case we deploy manually.

The deploy.sh script is stored in the deploy directory (deploy/deploy.sh).

#!/bin/bash
set -e
set -o pipefail

# Config
PROJECT_DIR="/the/location/where/your/project/lives/on/the/server"
DB_BACKUP_DIR="/the/location/where/your/database/bcps/live/on/the/server/db-bcp"
BACKUP_KEEP=5

# Paths to binaries
DRUSH="path/to/drush" # Discover with which drush
COMPOSER="path/to/composer" # Discover with which composer

# Multisite projects
PROJECTS=("default")   # Add more like: ("default" "site1" "site2")

cd "$PROJECT_DIR"

echo "=== Pull latest code ==="
git pull origin main

echo "=== Install dependencies ==="
$COMPOSER install --no-interaction --prefer-dist --optimize-autoloader

# Loop through multisites
for PROJECT in "${PROJECTS[@]}"; do
  echo "==============================="
  echo "=== Processing site: $PROJECT ==="
  echo "==============================="

  TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
  PROJECT_BACKUP_DIR="$DB_BACKUP_DIR/$PROJECT"

  mkdir -p "$PROJECT_BACKUP_DIR"

  echo "=== Maintenance mode ON ($PROJECT) ==="
  $DRUSH -l "$PROJECT" state:set system.maintenance_mode 1 -y
  $DRUSH -l "$PROJECT" cr

  echo "=== Database backup ($PROJECT) ==="
  BACKUP_FILE="$PROJECT_BACKUP_DIR/db-${PROJECT}-${TIMESTAMP}.sql"
  $DRUSH -l "$PROJECT" sql-dump > "$BACKUP_FILE"
  echo "Backup saved to $BACKUP_FILE"

  echo "=== Run Drupal deploy tasks ($PROJECT) ==="
  $DRUSH -l "$PROJECT" deploy -y

  echo "=== Maintenance mode OFF ($PROJECT) ==="
  $DRUSH -l "$PROJECT" state:set system.maintenance_mode 0 -y
  $DRUSH -l "$PROJECT" cr

  echo "=== Cleaning old backups ($PROJECT) ==="
  ls -1t "$PROJECT_BACKUP_DIR"/db-${PROJECT}-*.sql | tail -n +$((BACKUP_KEEP + 1)) | xargs -r rm --
  echo "Old backups cleaned for $PROJECT, keeping last $BACKUP_KEEP backups."

done

echo "=== Deployment complete for all projects ==="

The gitlab CI configuration file

A .gitlab-ci.yml file is the configuration file that defines automated CI/CD pipelines for a project in GitLab.

In short, it tells GitLab what jobs to run (e.g., build, test, deploy), when to run them (manual, on push, merge request, etc.) and how to run them (scripts, tools, environments).

It uses YAML syntax and the .gitlab-ci.yml is stored in the project root.

stages:
  - deploy

deploy:
  stage: deploy
  script:
    # Ensure ssh-agent is available
    - "which ssh-agent || ( apt-get update -y && apt-get install -y openssh-client )"
    - eval $(ssh-agent -s)

    # Set up SSH key securely
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$SSH_KEY" | base64 -d > ~/.ssh/gitlab_deploy
    - chmod 600 ~/.ssh/gitlab_deploy
    - ssh-add ~/.ssh/gitlab_deploy

    # Add remote host to known_hosts
    - ssh-keyscan -p $SSH_PORT $SSH_HOST >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

    # Run deployment script on remote host
    - ssh -i ~/.ssh/gitlab_deploy -p $SSH_PORT $SSH_USER@$SSH_HOST 'bash -s' < ./deploy/deploy.sh

    # Optional: remove the private key after use
    - rm -f ~/.ssh/gitlab_deploy
  when: manual
  only:
    - main

Server access

As Gitlab needs to access the server it needs the SSH_KEY, I advice you to generate a separate key on your server for this (without a passphrase).

ssh-keygen -t ed25519 -C "gitlab-deploy-key" -f ~/.ssh/gitlab_deploy_key

And add the public key to your server

cat ~/.ssh/gitlab_deploy_key.pub >> ~/.ssh/authorized_keys

We can't store the private key hidden and masked in gitlab so you should encode it:

cat ~/.ssh/gitlab_deploy_key | base64 -w0

This value we store in SSH_KEY in Gitlab.

The variables to define in Gitlab

You need to define 4 variables in Gitlab CI (under Settings > CI/CD > Variables):

  • SSH_HOST
  • SSH_KEY
  • SSH_PORT
  • SSH_USER

Deploying from Gitlab

Now that everything is setup we can deploy from Gitlab.

Go to your pipelines (Build > Pipelines) and click the play button.

When you click Deploy the deploy script is executed.

Disclaimer: In this Insight I don't touch rolling back deployments, deploying to a development or staging environment, etc. This is on purpose to limit the scope of the insight. That doesn't mean this isn't important and should be setup properly in your own workflow.

Join my newsletter

Get notified on new thoughts, insights, experiments, and lessons learned. It's (spam) free!

You might also be interested in