---
title: "Continuous Deployment, Infrastructure as Code, and Drupal: Part 1"
url: "/articles/continuous-deployment-infrastructure-code-and-drupal-part-1"
type: article
date: 2020-06-30
updated: 2021-01-12
---

# Continuous Deployment, Infrastructure as Code, and Drupal: Part 1

#  Continuous Deployment, Infrastructure as Code, and Drupal: Part 1 

 This article is part of one of a series that explores a basic continuous deployment workflow to deploy automatically using GitHub Actions and DigitalOceanâ&#128;&#153;s Kubernetes.

 By

 [  Juampy NR   ](/about/juampy-nr) 

 June 30, 2020

  

Having devoured DevOps books for the past year and a half, it was interesting to find that some of them contained the terms [Infrastructure as Code](https://en.wikipedia.org/wiki/Infrastructure_as_code) and [Continuous Deployment](https://en.wikipedia.org/wiki/Continuous_deployment). It was amazing to learn from the [DevOps Handbook](https://www.amazon.com/DevOps-Handbook-World-Class-Reliability-Organizations/dp/1942788002) that companies were already doing these things in the early 2000s. They achieved it via virtualization while nowadays many projects are doing it via containerization, yet the goals of these two concepts are still the same:

- Infrastructure as code: define all your infrastructure in files, down to the network interface level.
- Continuous deployment: make production deployments a chore. Small, frequent, and, most important: automatic.

To experiment with these two concepts, here is a Drupal demo application. You can check out the repository at <https://github.com/juampynr/drupal8-do>.

         ![Changes to Master Branch](/sites/default/files/styles/wide_xs/public/2020-06/changes-to-master-branch.png.webp?itok=j4GXDzsu)  https://github.com/juampynr/drupal8-do/actions

 The below screen recording shows a change being made in the configuration (the website's title), committed, and pushed. The GitHub Actions workflow, Kubernetes dashboard, and the Drupal application are then monitored:

[Embedded Video](http://www.lullabot.com/media/oembed?url=https%3A//www.youtube.com/watch%3Fv%3DsQd1TCVMf1w%26%20https%3A//youtu.be/sQd1TCVMf1w&max_width=0&max_height=0&hash=OqpAsJKKboCdeIHwSTBzka0EKNBSMmzjZl6ICVg0l18)

It works! It is far from perfect, but it is a giant step as it is an entirely different way of hosting and deploying a site. Here is the homepage running:

         ![The homepage running on a Kubernetes cluster](/sites/default/files/styles/wide_xs/public/2020-06/drupal-8-on-kubernetes.png.webp?itok=t8785iVi)  The homepage running on a Kubernetes cluster

In the next section, you will see an overview of the setup.

## The setup

Here is the root of the [GitHub repository](https://github.com/juampynr/drupal8-do) with some annotations:

- **.github** contains the workflow explained in the previous section.
- **definitions** contain the Kubernetes objects.
- **Dockerfile** defines how to build the project's image.

         ![Repository root](/sites/default/files/styles/wide_xs/public/2020-06/repository.png.webp?itok=QXS4RJP9)  The repository root

Here is a diagram that illustrates what happens behind the screens when pushing code changes to the GitHub repository:

         ![CD workflow](/sites/default/files/styles/wide_xs/public/2020-06/drupal-8-kubernetes-architecture-5.jpg.webp?itok=AH7j5t95)  CD workflow

In the above diagram, GitHub Actions takes care of building the project and creating a release. At the same time, Kubernetes is in charge of the deployment, detaching the database volume from the MySQL pod and re-attaching it to the pod containing the new configuration. The same happens for the Drupal pod, which has a volume to host public files. If you are new to Kubernetes, there is a great introduction with animated diagrams on the [official site](https://kubernetes.io/docs/tutorials/kubernetes-basics/).

Below is the GitHub Actions workflow, located in the repository at [.github/workflows/ci.yml](https://github.com/juampynr/drupal8-do/blob/master/.github/workflows/ci.yml):

```yaml
on:
  push:
    branches:
      - master
name: Build and deploy
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          fetch-depth: 1

      - name: Build, push, and verify image
        run: |
          echo ${{ secrets.PACKAGES_TOKEN }} | docker login docker.pkg.github.com -u juampynr --password-stdin
          docker build --tag docker.pkg.github.com/juampynr/drupal8-do/app:${GITHUB_SHA} .
          docker push docker.pkg.github.com/juampynr/drupal8-do/app:${GITHUB_SHA}
          docker pull docker.pkg.github.com/juampynr/drupal8-do/app:${GITHUB_SHA}

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Save cluster configuration
        run: doctl kubernetes cluster kubeconfig save drupster

      - name: Deploy to DigitalOcean
        run: |
          sed -i 's|<IMAGE>|docker.pkg.github.com/juampynr/drupal8-do/app:'${GITHUB_SHA}'|' $GITHUB_WORKSPACE/definitions/drupal-deployment.yaml
          sed -i 's|<DRUPAL_CRON>|${{ secrets.DRUPAL_CRON }}|' $GITHUB_WORKSPACE/definitions/drupal-deployment.yaml
          kubectl apply -k definitions
          kubectl rollout status deployment/drupal

      - name: Update database
        run: |
          POD_NAME=$(kubectl get pods -l tier=frontend -o=jsonpath='{.items[0].metadata.name}')
          kubectl exec $POD_NAME -c drupal -- vendor/bin/robo files:configure
          kubectl exec $POD_NAME -c drupal -- vendor/bin/robo database:update
```

In essence, it builds a Docker image, pushes it, and then deploys it along with any other configuration changes into a Kubernetes cluster hosted on DigitalOcean.

# Debugging

While setting up the workflow, there was a time when a check seemed necessary to find out why the site was not working. A search for how to open an SSH connection with the cluster turned up nothing. Further research revealed that you don't use SSH to connect to the cluster, but instead, you use `kubectl` to [open a shell against a container](https://kubernetes.io/docs/tasks/debug-application-cluster/get-shell-running-container/).

## Kubernetes contexts (aka cluster management)

Kubernetes uses the concept of [context](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context) to manage cluster configurations. For example, here is the output of listing contexts locally:

```bash
juampy@carboncete:~:$ kubectl config get-contexts
CURRENT  NAME              CLUSTER           AUTHINFO               
*        do-sfo2-drupster  do-sfo2-drupster  do-sfo2-drupster-admin
         minikube          minikube          minikube      
```

**do-sfo2-drupster** is the current context, referencing the DigitalOcean cluster used for this article. The second one in the list refers to a cluster created by [minikube](https://kubernetes.io/docs/setup/learning-environment/minikube/), a development tool to create and run a Kubernetes cluster locally.

When the GitHub Actions workflow in the above section runs the command **doctl kubernetes cluster kubeconfig save drupster**, it is downloading the cluster configuration, saving it at **~/.kube/config**, and setting it as the default context. From that point, all kubectl commands will run against such cluster.

[*doctl*](https://github.com/digitalocean/doctl) *is the command-line interface to interact with DigitalOcean.*

## Opening an interactive session against a container

With the cluster configuration in place for kubectl, here is how to run a command against the container running Drupal. First, you need to identify the pod that hosts the container that runs Drupal, which you can figure out via **kubectl get pods**:

```bash
juampy@carboncete:~:$ kubectl get pods
NAME                            READY   STATUS      RESTARTS   AGE
drupal-5b4f9996cf-dlvtw         1/1     Running     0          29h
drupal-cron-1590506940-zxglx    0/1     Completed   0          3m10s
drupal-cron-1590507000-g25g2    0/1     Completed   0          2m10s
drupal-cron-1590507060-7vmns    0/1     Completed   0          70s
drupal-cron-1590507120-rnn6t    0/1     Completed   0          10s
drupal-mysql-6594bfd66b-9j6zd   1/1     Running     0          29h
```

The pod to look for is **drupal-5b4f9996cf-dlvtw** since the other pods are for cron runs and MySQL. With the pod identifier you can run commands against the **drupal** container running within it:

```bash
juampy@carboncete:~:$ kubectl exec drupal-5b4f9996cf-dlvtw -c drupal -- ls
Dockerfile Makefile README.md RoboFile.php composer.json composer.lock config definitions docker-compose.yml docs scripts traefik.yml vendor web
```

We just sent an **ls** command to the container which returned the list of files and directories of the application. If run several commands against the Drupal container is required, you can open an interactive session:

```
juampy@carboncete:~:$ kubectl exec drupal-5b4f9996cf-dlvtw -c drupal -i -t -- bash
root@drupal-5b4f9996cf-dlvtw:/var/www/html# ls
Dockerfile  Makefile  README.md  RoboFile.php  composer.json  composer.lock  config  definitions  docker-compose.yml  docs  scripts  traefik.yml  vendor  web
```

As a final note, this is a learning process. If you have any tips, feedback, or want to share anything about this topic, please post it here as a comment or tweet us @lullabot or @juampynr. Thanks in advance!

The next article in this series will cover each of the GitHub Actions steps in detail.

## Acknowledgments

- Inspiring articles written by [Alejandro Moreno](https://medium.com/@alejandromoreno/deploying-your-ddev-containers-in-digitalocean-or-aws-with-kubernetes-507df41b4816) and [Jeff Geerling](https://www.jeffgeerling.com/blog/2018/running-drupal-cron-jobs-kubernetes/).
- Tutorials that help you get a mental map of how to fit each of the pieces together in the puzzle: 
    - [How To Deploy a PHP Application with Kubernetes on Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-php-application-with-kubernetes-on-ubuntu-18-04)
    - [Example: Deploying WordPress and MySQL with Persistent Volumes](https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/)
- This [presentation](https://socketwench.github.io/return-of-the-clustering/) by Tess ([socketwench](https://twitter.com/socketwench)) is stellar in every single way.

 Published in:

- [  Deployment   ](/topics/deployment)
- [  System Administration   ](/topics/system-administration)

 

   You must have JavaScript enabled to use this form.

## Get in touch with us

### Tell us about your project. We'd love to hear from you!

 First Name  

 Last Name  

 Your email address  

 Your organization  

 How you heard about us  

 Tell us more 

     Leave this field blank