How to integrate Continuous Deployment and Continuous Integration in a shared hosting
Tags: webdev, devops, productivity, programming
Published: 2022-11-29 23:55:38

We have all used, and continue to use, Shared Hostings, it is something that will never change even if technology advances with servers in the cloud, and the reason is because they are: **super cheap**, I have even gotten some of $0.50 per month.

Many believe that to have automated deployment of your application on your server you need to have something in the cloud, in AWS, Azure or Google Cloud Platform. But this is not true, this can be automated even on servers as limited (so much so that some do not allow you to connect via SSH) such as Shared Hosting.

Even if your shared hosting does not allow you to connect via SSH and you need to execute some command line steps before uploading your application (for example to configure or install packages) you can do it with a [PHP Shell](https://github. com/flozz/p0wny-shell), all such servers support PHP, so you can use that to execute commands in a shell even if the server doesn't support SSH connection.

## What is CI / CD? 🤔

- **Continuous Integration**: It is the process in which each time you make a commit to your repository, it executes a series of steps to verify that your changes do not break existing functionalities, either by compiling the project (if it compiles, it works), and also by executing unit tests (if all the tests are met, everything is correct).
- **Continuous Deployment**: It is the process that comes after the Continuous Integration, although not immediately, it may be in specific versions of your application that this is executed; that is to say, that the application is not uploaded in each commit that we make, but in a new version, 1.1, which when it is created is because all the functionalities have already been tested and they comply with the requirements. This process describes uploading your app automatically to production without lifting a finger.

## What do I need that for? 🙄

For two reasons: **save time** and **avoid mistakes**, we are all human, and humans make mistakes, machines don't. The machines only do one thing and they do it wonderfully, and that is to follow orders.

If you tell a machine to do the automatic deploy, it will do it. If you tell a human that he is tired of working 8 hours without a break, and maybe he went to bed late last night, that human might make a mistake in uploading some of the files manually. And this will make you **lose money**.

## How to integrate that? 🧐

All shared hosting providers offer different functionality, some support Git, which you can use to upload your application, but others don't, so you just have to use the good old working one, **FTP**.

Why FTP? If you have integrated CI / CD in a VPS or a cloud server, you will surely do it in one of these ways:

- Place the link of your repository and the server does the work, in the case of serverless hosting.
- Configure the deploy by yourself using rsync over SSH or any other method that is not over FTP.

What we will do will be something similar to the second, only that this deploy will not be by rsync but by FTP.

But doesn't this make it less secure? What the developers default to is rsync, due to its security, file uploads are done via SSH protocol, which makes it super secure. But all shared hosting supports FTP over SSL, so it's still just as secure. Your data will not be exposed to any type of attack.

## Step 1. Identify project 🛠️

![](https://cdn-icons-png.flaticon.com/256/4946/4946348.png)

Any type of project can be uploaded in this way, but it will depend on your server if it can execute it, for example you can upload a Ruby on Rails project with what I will show you today, but your server may not support Ruby so that you will not be able to run your application.

### Laravel / Lumen

If your application is made in Laravel, you first need to do all the steps in this guide, and then make a symbolic link of your project's "public" folder to the `httpdocs` or `htdocs` folder, this can be done via SSH (if your server supports it), or via a [PHP Shell](https://github.com/flozz/p0wny-shell).

In this guide at the end I will explain how to do this for applications in Laravel.

### PHP

Depending on the file structure of your PHP project:

- If you have the entrypoint of the application in a "public" or "bootstrap" folder, you have to make a symbolic link to the `httpdocs` or `htdocs` folder.
- If you have all your PHP files in the main directory you don't have to do anything else, just upload your folder with the files and that's it.

### WordPress

In the event that you are developing a custom theme for your Wordpress, all you have to do is follow the steps in this guide and in the step of creating the FTP user you place the `wp-content/themes/` directory of your server.

## Step 2. Create FTP user

![](https://cdn-icons-png.flaticon.com/256/8648/8648722.png)

You can skip this step and use the default FTP user provided by the hosting, but I prefer to do this for two reasons:

- In case of password theft, this FTP user only has access to where my application's files are (not to the entire system), and if they are deleted, I simply have to change the passwords and run my Continuous Deployment again to have the application again online.
- I don't have to specify any absolute paths or any directories in my Continuous Deployment file, so if my code is open source users can't see where my application is hosted on my server.

So we create a user named `git_ftp` or something like this, with a strong password, and we set it to only have access to the path we want our application to be on.

For a custom WordPress theme it should be the `wp-content/themes` path.

For a PHP system it might be a subdirectory in the main folder, like `httpdocs/php-app`, or the main folder itself, `httpdocs`. The same goes for Laravel.

## Step 3. Automating with GitHub Actions 💻

![](https://miro.medium.com/max/600/0*w5Fsp29pbWIUpW7Q.png)

You can use any other CI/CD service, such as **Azure Pipelines**, or **Travis CI**, but to use GitHub Actions you don't need to create a separate account elsewhere, you just need your GitHub account, and nothing more. Even for private repositories (although it gives you 2,000 minutes per month, which is honestly more than enough for this).

The first thing you need to do is create a folder in your repository called `.github`, and create another folder inside this called `workflows`, and here create a file called `continuous-deployment.yml`, and put this content in the:

```yaml
name: Continuous Deployment

on:
    push:
        branches:
            - master

jobs:
    deploy:
        runs-on: ubuntu-latest

        steps:
            - uses: actions/checkout@v3

            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: 16
                  cache: "npm"

            - name: Install Node.js dependencies
              run: npm ci

            - name: Build project
              run: npm run build

            - name: Deploy to the Server
              uses: SamKirkland/FTP-Deploy-Action@4.0.0
              with:
                  server: ${{ secrets.FTP_SERVER }}
                  username: ${{ secrets.FTP_USERNAME }}
                  password: ${{ secrets.FTP_PASSWORD }}
                  local-dir: ./
                  server-dir: ./
```

You can customize this file however you want, by default it has a step for the project to be compiled with Node.js, this is the case of projects with React.js, Laravel, etc. In case your project doesn't need that you can delete it.

You can also add steps to clear Cloudflare's cache or to do some extra command line steps that you do after each deploy, etc.

In addition to this, you can change the `server-dir` field in the Deploy step, there they will place the path where the files will go on the server (e.g. `wp-content/themes/`), and the ` local-dir` the directory to upload (e.g. the `build` folder for React.js projects).

Now, if you realize there is no username or password here, how does it communicate with my server? We must define that through the **GitHub Secrets** in the repository.

**Note**: in case your **repository is private** and you don't have a paid GitHub account you must change those `username` and `password` fields with the respective values ​​in the YAML file. But since it is your private repository, and only people from your team work there will be no risk of password theft, and if there is, that's why an isolated FTP user is created with only access to your application folder. Therefore, you can skip the next steps of adding GitHub Secrets.

Now, you have to go to your repository settings and go to "Secrets" > "Actions":

![](https://i.imgur.com/1gK7QxG.png)

And then in the "New repository secret" button you add the following values one by one:

- FTP_SERVER: The IP or domain of your server to connect via FTP.
- FTP_USERNAME: The name of the FTP user that was created in the previous step.
- FTP_PASSWORD: The password of the FTP user that was created in the previous step.

![](https://i.imgur.com/r6kYm7v.png)

And voila, with this you have integrated Continuous Deployment in your application. Just upload the changes and your application will be online without doing anything more than a simple push.

## Step 4. Continuous Integration

This step should be essential, because sometimes by mistake of the developers by not testing their code before they upload the code and it goes directly to production without having executed the tests that prove that the code does not break any functionality. But not all projects have tests, either because they are too small to have them, or because programmers are too lazy to write them.

However, you can implement it by creating a file in the folder we saw earlier, `.github/workflows` called `continuous-integration.yml`, and placing this content in it:

```yaml
name: Continuous Integration

on: [push, pull_request]

jobs:
    format:
        runs-on: ubuntu-latest

        steps:
            - uses: actions/checkout@v3

            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: 16
                  cache: "npm"

            - name: Install Node.js dependencies
              run: npm ci

            - name: Run Prettier
              run: prettier --write .

            - name: Commit changes
              uses: stefanzweifel/git-auto-commit-action@v4
              with:
                  commit_message: "style: apply formatting changes"
                  branch: ${{ github.head_ref }}

    test:
        runs-on: ubuntu-latest

        steps:
            - uses: actions/checkout@v3

            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: 16
                  cache: "npm"

            - name: Install Node.js dependencies
              run: npm ci

            - name: Run tests
              run: npm run test
```

For now, what it is doing is that in each commit you make to your repository and each pull request it formats the code with [Prettier](https://prettier.io) although you can replace it with any other code formatter such as [standardjs] (https://standardjs.com), or use a linter like [ESLint](https://eslint.org).

In case your project is with PHP you must add some extra steps to this file in the `format` job to run the tests with PHPUnit:

```yaml
- name: Setup PHP
  uses: actions/setup-php@v2
  with:
      php-version: "8.1"

- name: Run PHPUnit
  run: composer run test
```

And that's it, you already have Continuous Integration implemented in your project, now each commit you make will be formatted to meet code style standards, and will be tested to verify that it doesn't break any functionality.

It even works with Pull Requests, so you don't have to test the pull request code on your PC, GitHub tells you if the tests failed or not.

## Extra step. If your project is made with Laravel

Laravel runs in an entrypoint in the "public" folder that this is the folder that should go in your `httpdocs` but this folder can't just be copied, it has to be a _symbolic link_, because this entrypoint is just the input to your application, all the code is in the other folders. That is why to run Laravel on a Shared Hosting and also integrate Continuous Deployment you must do a couple of other things:

#### Step 1. FTP User

Debes de crear una carpeta en el home del servidor llamada "repositories" y allí crear una carpeta con el nombre de tu proyecto.

El usuario FTP que creamos debes de actualizar la ruta a la que puede acceder por la ruta de esta carpeta.

#### Step 2. Execute Continuous Deployment pipeline

We must upload the changes via Git to our repository so that the CD is executed and our application is already uploaded in the "repositories/your-app" folder, with "your-app" for the name of your application.

#### Step 3. Run commands in a PHP shell or connect via SSH

If your server supports SSH simply connect to it and skip this below:

- Download the "shell.php" file from this [repository](https://github.com/flozz/p0wny-shell), and upload it to your server.
- Go to the browser and navigate to your website and put the path "/shell.php" (e.g. `https://www.google.com/shell.php`).

Run the following commands:

```bash
$ ln -s ~/repositories/your-app/public ~/httpdocs/your-app
```

You have to replace the name of "your-app" with that of your application, and in case "httpdocs" is not the main public folder you must also change it. Also, in case you want to run your Laravel application when only its domain is opened (as the main application) you must delete the httpdocs folder and make a symlink to it just like in the command that it explains.

## Conclusion

Although our server is a Shared Hosting, this does not prevent us from enjoying the automation and the things that the cloud already offers you by default, it simply takes a little more configuration but it will save you a lot of time and money later.

You can customize this to your liking, adding more steps to this Continuous Deployment, from sending the client an email telling them to check out the new version of the application, to compiling your project written in C++ and distributing the binaries in a Github Release. **With code the limit is the sky! ✨**.