Deploy your PHP Codebase with Ansible and GitHub Actions
Published on by Anton Röhm
This tutorial will show you how you can automate the deploy of your PHP codebase with the help of GitHub Actions and Ansible.
There are a lot of different approaches when it comes to deploying your PHP application to your production server(s). Nowadays, most projects use git and live on a platform like GitHub, GitLab or Bitbucket. But how do you roll out your changes to your server? You could manually SSH into your server after each change and pull the latest code from GitHub, you could use GitHub webhooks to do this automatically for you, or you could use an external software to manage and do all deploys for you. However, today I'd like to show you how you can automate your deploys with a tool called Ansible and GitHub Actions. Ansible is a powerful open-source automation software written in python which simplifies the process of setting up and managing remote machines in an automated way.
Generally, Ansible works like this: You need to create an Ansible Inventory which contains the information of your server(s) and you need to set up a way for Ansible to log into these servers. Ideally, you create a new user on your server just for Ansible which logs in with an SSH key for added security. Everything else gets configured with YAML files.
You can do a lot of things with Ansible: Set up your (web-)servers, keep them up-to-date, install software, automatically scale up or down and so much more - however, this guide will only focus on how to automatically deploy the latest version of your code with Ansible. We'll prepare our servers and set up a GitHub Action which will automatically run Ansible and deploy our code to our server on each push to our production
branch.
Preparations
For Ansible to be able to connect to and work on your server, you need to do two things: install some required software (Python and Git) and create a separate admin user on your server for Ansible. To do this, connect to your server via SSH and run the following commands:
# 1. Install Python and Gitsudo apt install python3 git # 2. Create a superuser named "ansible"sudo useradd -m -G sudo ansiblesudo passwd ansible
Now that you have an ansible
user on your server, you should also create an SSH key-pair for it to use instead of a password, so it can connect more securely. Make sure to enable this SSH key for the newly created ansible
user, not for your default root
user on the server:
If you have multiple servers, repeat this step for each one of them (ideally, use the same SSH public key for the Ansible users on all servers).
Set up the Inventory
The so-called Ansible Inventory holds all information about your server(s) and any possible additional variables. You can either put the content in a file, or you can put the content in a GitHub "Repository Secret" and get the content later in your GitHub Action. I'd recommend going with the second option, because some of the content of the inventory (for example the IP addresses of the servers) may be considered confidential and shouldn't appear in your git version control (especially when your server is behind a Cloudflare firewall or something similar).
The structure of the inventory is divided into groups of servers and is pretty straight-forward:
---all: vars: # here you can put any global variables you might need in Ansible playbooks webserver: # the name of our first server group, you can rename this group or add more groups as you like hosts: 254.254.254.254: # replace this with the IP address of your server. If you have multiple servers, just add them in the following lines
Create the Playbook
To tell Ansible what to do when deploying our code, we need to create a so-called Ansible Playbook. It's a YAML file with instructions for Ansible separated into different tasks (steps). I'd recommend you to create a new directory for your Ansible Playbook in your PHP code repository. You could also create a new repository just for your Ansible Playbooks and set it up so pushes to your main branch from your PHP code repository triggers the execution of the GitHub Action in your Ansible repository - this is great if you want to keep these things more separated, but it's a bit more complicated to set up. That's why I'd recommend you to go the easy route for now and just create a directory in your main repository.
Make sure to configure your webserver to forbid accessing your Ansible folder publicly.
In this directory, create a new YAML file - we'll call it deploy-playbook.yml
. In this playbook, we tell Ansible to do the following things:
- Clone our PHP code repository into a temporary folder on the server
- Optional: Do any additional steps we might need to do on deploy (e.g. install composer packages)
- Copy the code to the right destination (in most cases the
DocumentRoot
of your webserver) - Update the file permissions (so they are accessible by the webserver)
We'll add for each of these steps a task in the playbook:
---- hosts: webserver become: yes become_user: root vars: temp_repo_path: "/tmp_repo" # path to the temporary downloadfolder of the repo tasks: # Get the latest code from your GitHub code repository - name: Get latest Application Codebase git: repo: https://github.com/AnTheMaker/Pesto.git # place the HTTPS git URL of your codebase here version: main # replace this with the name of your "production" branch (in most cases "main" or "master") dest: "{{ temp_repo_path }}" single_branch: yes accept_hostkey: true depth: 1 # if your git repository is private, you'll need to create a GitHub deploy key (https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys), upload the private key to your server and uncomment the following line: # key_file: ~/github_deploy_private_key.key register: code_upload # set a variable in Ansible if this task succeeds # do any additional tasks you may need to do. E.g. install composer packages: (uncomment/change as you like)# - name: Install Composer Packages# composer:# command: "install"# working_dir: "{{ temp_repo_path }}"# environment:# COMPOSER_NO_INTERACTION: "1"# when: code_upload.changed # only run this task when the code has changed - name: Replace live Codebase delegate_to: "{{ inventory_hostname }}" synchronize: src: "{{ temp_repo_path }}" dest: /var/www/ recursive: yes delete: yes when: code_upload.changed # only run this task when the code has changed - name: Update Permissions file: path: /var/www/html state: directory recurse: yes # change the following two lines to the user of your webserver owner: www-data group: www-data
Set up GitHub Actions
Now that we have an Ansible Playbook with instructions on how to deploy our code and Inventory containing our server IPs, we just need to set up GitHub actions to automatically run Ansible whenever we push to our production
branch (or whatever you may have called it in your repository).
Luckily, there already exists a great pre-made GitHub action to do just that, called Run Ansible Playbook - we just need to set it up to work with our setup.
To do that, we need to create a GitHub Action YAML file in your repository under the following path: .github/workflows/main.yml
name: Deployon: # Triggers this Action on push or pull request events on the "main" branch and when manually requested from the "Actions" tab push: branches: [ main ] workflow_dispatch: jobs: deploy_code: runs-on: ubuntu-latest steps: - name: Run Ansible playbook uses: dawidd6/action-ansible-playbook@v2.5.0 with: playbook: ansible/deploy-playbook.yml # path to your Ansible playbook directory: ./ key: ${{ secrets.ansible_ssh_private_key }} # the ssh private key for ansible to use to connect to the servers, stored as "ansible_ssh_private_key" in the GitHub secrets inventory: ${{ secrets.ansible_inventory }} # the ansible inventory to use, stored as "ansible_inventory" in the GitHub secrets
Saving this workflow file and pushing it to GitHub should automatically enable this Action. From now on, whenever you push to production (or manually request a deploy from your Repositories "Actions" tab), this Action should be executed which will run Ansible, connect to your server, and deploy your new code. When the action is running, you can click on the "Actions" tab and view the currently running action (together with a full live log of the Ansible run with any possible errors or warnings).
Further Resources
19-year-old self-taught full stack web developer from Germany. Creator of SpaceHey. Photo: Walzberg/Tincon