Setup Github Actions for Symfony project

28 Jan 2021

Hello! Today I would like to share Github Actions configuration and usage experience. I was interested in this tool for a while. As soon as I use CI/CD, docker containers etc. for commercial projects, but not for my personal/own pet projects

Main use cases:

  • Simplify project deploy and local developer environment
  • Continuously build project and run tests
  • Automated deploy

Initially I configured Github Actions for this blog. Then I also made it for one of my open source projects - placeholder-service. Idea behind is to make it more clear and share all configs.

Docker

Firstly I dockerized app for local development environment using this tutorial.

Main advantages:

  • All environment is created from scratch. I need to do almost same things for Github Actions.
  • Development environment is isolated from local environment
  • Only a few commands needed to run this app on new machine
  • It's easy to change versions of linux distro, soft, dependencies etc.

Finally, I have suchdocker-compose.yml.

version: '3'
services:
  php-fpm:
    build:
      context: ./docker/php-fpm
    environment:
      - DOCKER_ENVIRONMENT=true
      - APP_SECRET=${APP_SECRET}
    volumes:
      - ./:/var/www
  nginx:
    build:
      context: ./docker/nginx
    volumes:
      - ./:/var/www
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/sites/:/etc/nginx/sites-available
      - ./docker/nginx/conf.d/:/etc/nginx/conf.d
      - ./docker/logs:/var/log
    depends_on:
      - php-fpm
    ports:
      - "16880:80"
      - "16843:443"

Composer scripts

Then I moved some commands to composer scripts to make it easier for remember and usage.

So, I have such composer.json.

{
    "name": "antonshell/placeholder-service",
    "description": "There is a self hosted service for images placeholders generation",
    ...
    "scripts": {
        ...
        "test": [
            "php bin/phpunit"
        ],
        ...
    }
}

For example, for run tests I execute composer test instead of php bin/phpunit. In github actions we are going to use shortcuts.

Github Actions - tests

For tests configuration I used this tutorial. I created .github/workflows/ directory and added tests.yml file in the repository.

Tests.yml file looks like that:

name: Tests
on: [push]
jobs:
  php-unit-and-functional-tests:
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: true
      matrix:
        php-versions: ['7.4', '8.0']
    steps:
      # —— Setup Github actions —————————————————————————————————————————————
      # https://github.com/actions/checkout (official)
      - name: Git checkout placeholder-service
        uses: actions/checkout@v2
      # https://github.com/shivammathur/setup-php (community)
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          coverage: none
          tools: composer:v2
          extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, dom, filter, gd, iconv, json, mbstring, pdo
        env:
          update: true
      - name: Check PHP Version
        run: php -v
      # —— Composer —————————————————————————————————————————————————————————
      - name: Validate composer.json and composer.lock
        run: composer validate
      - name: Get composer cache directory
        id: composer-cache
        run: echo "::set-output name=dir::$(composer config cache-files-dir)"
      - name: Cache composer dependencies
        uses: actions/cache@v1
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-
      - name: Install Composer dependencies
        run: composer install
      # —— Symfony ——————————————————————————————————————————————————————————
      - name: Check Symfony requirements
        run: vendor/bin/requirements-checker
      - name: Check the Symfony console
        run: bin/console -V
      # —— Code style —— (Based on this package: https://github.com/OskarStark/php-cs-fixer-ga)
      - name: PHP-CS-Fixer
        uses: docker://oskarstark/php-cs-fixer-ga
        with:
          args: --config=.php_cs.dist --diff --dry-run
      ## —— Tests ———————————————————————————————————————————————————————————
      - name: Run functionnal and unit tests
        run: |
          cp .env.ci .env.test
          cp .env.ci .env
          cp phpunit.ci.xml phpunit.xml
          composer test

Each file in workflows directory describes some process. There is a workflow name, operational system(ubuntu 20.04), and when to run it(on push). There is also sequence of actions for preparing environment - setup PHP, clone a repository, setup dependencies etc.

When environment is ready, we are checking codestyle and run tests. Tests workflow triggered every time we are pushing changes to repository.

Github Actions - deploy

For deploy configuration I used this tutorial and this action.

1 . Created separate workflow - ssh_deploy.yml.

name: SSH Deploy
on:
  workflow_dispatch:
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      # Install PHP
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.0'
          coverage: none
          tools: composer:v2
          extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, dom, filter, gd, iconv, json, mbstring, pdo
        env:
          update: true
      - name: Check PHP Version
        run: php -v
      # Install backend dependencies (Composer)
      - name: Validate composer.json and composer.lock
        run: composer validate
      - name: Get composer cache directory
        id: composer-cache
        run: echo "::set-output name=dir::$(composer config cache-files-dir)"
      - name: Cache composer dependencies
        uses: actions/cache@v1
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-
      - name: Install Composer dependencies
        run: composer install
      # Prepare .env file for production
      - name: Make production envfile
        uses: SpicyPizza/create-envfile@v1
        with:
          envkey_APP_ENV: prod
          envkey_APP_DEBUG: false
          envkey_APP_SECRET: ${{ secrets.APP_SECRET }}
          file_name: .env
      # Copying files and artifacts via SSH
      - name: Copying files to server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          passphrase: ''
          rm: true
          source: "./"
          target: ${{ secrets.REMOTE_TARGET }}
      # Run commands on production
      - name: Executing remote ssh commands
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          passphrase: ''
          script: rsync -a --exclude={'var','temp'} --delete ${{ secrets.REMOTE_TARGET }} ${{ secrets.REMOTE_TARGET_DEPLOY }}

Project build on a temporary server, then copied with ssh to main server.

2 . I used Github Secrets for storing secret data like server address, ssh key, passwords etc. There are more details about Github Secrets. Secret variables values can be used like that:

host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}

3 . Created ssh key for deployment user and added it to Github Secrets. Saved id_rsa file content to SERVER_SSH_KEY variable.

ssh-keygen -m PEM -t rsa -b 4096 -f ./id_rsa
cat id_rsa
cat id_rsa.pub

4 . Creted separate user on remote server and added ssh key. Added id_rsa.pub file content to /home/deployment/.ssh/authorized_keys.

adduser deployment
usermod -aG www-data deployment

mkdir /home/deployment/.ssh/
nano /home/deployment/.ssh/authorized_keys
chown deployment:deployment /home/deployment/.ssh/authorized_keys
chmod 600 /home/deployment/.ssh/authorized_keys

5 . Checked login for deployment user. This also might be useful for debug deployment process and run commands as a deployment user.

ssh -i ./id_rsa deployment@{{remote_server_ip}}

6 . Created directories needed for deploy process. Firstly project copied to temporary directory. Then copied to web directory.

cd /var/www
sudo mkdir /var/www/deployment/
sudo mkdir /var/www/deployment/placeholder-service
sudo chown -R deployment:deployment /var/www/deployment/
sudo chmod -R 775 /var/www/deployment/

sudo mkdir /var/www/placeholder-service
sudo chown -R deployment:deployment /var/www/placeholder-service
sudo chmod -R 775 /var/www/placeholder-service

7 . Deployment triggered manually from "Actions" tab.

Github actions - extra

There are some useful, or just interesting extensions for Github Actions.

It also would be interesting to visit Github Actions Marketplace and check extensions for PHP projects. Finally, I recommend reviewing this list: https://github.com/sdras/awesome-actions.

Demo project is available on github. That's all for today. Thank you for your attention!