Deploy your PHP Codebase with Ansible and GitHub Actions


January 24th, 2022

Deploy your PHP Codebase with Ansible and GitHub Actions

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.


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 Git
sudo apt install python3 git
# 2. Create a superuser named "ansible"
sudo useradd -m -G sudo ansible
sudo 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:

# 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: # 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:

  1. Clone our PHP code repository into a temporary folder on the server
  2. Optional: Do any additional steps we might need to do on deploy (e.g. install composer packages)
  3. Copy the code to the right destination (in most cases the DocumentRoot of your webserver)
  4. 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
temp_repo_path: "/tmp_repo" # path to the temporary downloadfolder of the repo
# Get the latest code from your GitHub code repository
- name: Get latest Application Codebase
repo: # 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 (, 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:
# when: code_upload.changed # only run this task when the code has changed
- name: Replace live Codebase
delegate_to: "{{ inventory_hostname }}"
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
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: Deploy
# Triggers this Action on push or pull request events on the "main" branch and when manually requested from the "Actions" tab
branches: [ main ]
runs-on: ubuntu-latest
- name: Run Ansible playbook
uses: dawidd6/action-ansible-playbook@v2.5.0
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

Filed in:

Anton Röhm

19-year-old self-taught full stack web developer from Germany. Creator of SpaceHey. Photo: Walzberg/Tincon