GitHub telah merilis produk GitHub Actions dengan tujuan untuk memudahkan software developer melakukan Continuous Integration (CI) dan Continuous Delivery (CD). Dalam praktiknya, kita bisa men-deploy program kita ke server tanpa harus menggunakan FTP.

Cukup menggunakan perintah git push ke GitHub maka proses deployment ke server akan diproses oleh GitHub mulai dari menginstal dan meng-compile aset seperti aset dari Node Package Manager (NPM). Kemudian, hasil dari compile aset tersebut tinggal dikirim ke server. Sehingga, server kita tidak perlu menginstal NodeJS karena proses menginstal dan meng-compile aset. Lumayan kan menghemat beban kerjaan server? ๐Ÿ˜

Tulisan kali ini, saya akan menjelaskan CI/CD project Laravel menggunakan GitHub Actions. Pastikan Anda sudah memiliki VPS. Pada tulisan ini saya menggunakan VPS dari Digital Ocean.

  1. Buat user bernama deployer tanpa akses root dan tidak perlu diberikan password.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Create user deployer with root account
adduser deployer

# Append (-a) a secondary group (-G) "www-data" to user "deployer"
usermod -a -G www-data deployer

# See groups assigned to user "deployer"
groups deployer

# Add ACL permission in /var/www

# check setfacl exists
which setfacl

# If doesn't exist:
sudo apt-get install -y acl

# Inspect current ACL's
getfacl /var/www

# Set current and default ACL's for /var/www
sudo setfacl -Rm g:www-data:rwx,d:g:www-data:rwx /var/www
  1. Pindah ke user deployer dengan perintah su deployer dan buatlah SSH key dengan mengikuti panduan dari GitHub tentang cara membuat SSH key.

  2. Setelah berhasil membuat SSH key, selanjutnya lakukan duplikat konten public key SSH ke authorized keys.

1
2
3
# Sebagai user `deployer` di server
# Salin isi dari public key ke authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
  1. Duplikat isi private key dan kita akan taruh di project GitHub di menu Settings => Secrets dan beri nama SSH_PRIVATE_KEY.
1
2
3
4
# Masih sebagai user `deployer` di server
# Salin blok teks private key
cat ~/.ssh/id_rsa
# Blok isi dari private key tadi, copy dan tempel di GitHub project kita seperti gambar di bawah.
Memasang ssh private key di GitHub project
  1. Untuk mencegah tidak ada MITM (Man in the middle) attack, kita harus mendapatkan SSH fingerprint VPS kita dengan perintah ssh-keyscan.
1
2
3
4
# Masih sebagai user `deployer` di server
# Jalankan perintah ini di dalam server kita
ssh-keyscan -t rsa ip_server_kita
# Contoh: ssh-keyscan -t rsa 123.456.781.901

Setelah itu, salin hasil dari ssh-keyscan ke project GitHub di menu Settings > Secrets dan beri nama SSH_KNOWN_HOSTS seperti gambar di bawah.

Memasang ssh known hosts di GitHub project
  1. Buat variabel secrets bernama DOTENV di menu Settings > Secrets. Isi nilai dari variabel ini adalah dari file .env di proyek Laravel. Sesuaikan nama aplikasi, mode env (production atau development), koneksi database. Pastikan juga nilai APP_KEY sudah terisi!

  2. Menginstal Deployer di project Laravel yang ada di komputer lokal, alat untuk men-deploy project ke server dengan perintah di bawah ini.

1
composer require deployer/deployer deployer/recipes
  1. Buat file deploy.php dengan perintah ./vendor/bin/dep init dan ikuti petunjuk yang diberikan, pilih Laravel sebagai framework yang dipakai.

  2. Buka file deploy.php dan salin Gist di bawah ini ke file deploy.php.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

namespace Deployer;

// Include the Laravel & rsync recipes
require 'recipe/laravel.php';
require 'recipe/rsync.php';
// Cleanup each 10 releases.
require 'recipe/deploy/cleanup.php';

set('application', 'dep-demo');
set('ssh_multiplexing', true); // Speed up deployment

set('rsync_src', function () {
    return __DIR__; // If your project isn't in the root, you'll need to change this.
});

// Configuring the rsync exclusions.
// You'll want to exclude anything that you don't want on the production server.
add('rsync', [
    'exclude' => [
        '.git',
        '/.env',
        '.github',
        'deploy.php',
    ],
]);

task('deploy:secrets', function () {
    // 'DOTENV' berasal dari langkah ke-6. Pastikan nama variable harus sama!
    file_put_contents(__DIR__ .'/.env', getenv('DOTENV'));
    upload('.env', get('deploy_path') . '/shared')
});


// Hosts
host('production.app.com') // Name of the server
    ->hostname('123.456.781.901') // Hostname or IP address
    ->stage('production') // Deployment stage (production, staging, etc)
    ->user('deployer') // SSH user
    ->set('deploy_path', '/var/www/laravel-project') // Deploy path
    ->set('http_user', 'www-data');

after('deploy:failed', 'deploy:unlock'); // Unlock after failed deploy

desc('Deploy the application');
task('deploy', [
    'deploy:info',
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'rsync', // Deploy code & built assets
    'deploy:secrets', // Deploy secrets
    'deploy:shared',
    'deploy:vendors',
    'deploy:writable',
    'artisan:storage:link', // |
    'artisan:view:cache',   // |
    'artisan:config:cache', // | Laravel specific steps
    'artisan:optimize',     // |
    'artisan:migrate',      // | Run artisan migrate if you need it, if not then just comment it!
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
]);
  1. Buat folder .github/workflows di root project Laravel dan file deploy.yml di dalam folder workflows.
1
2
3
mkdir -p .github/workflows
cd .github/workflows
touch deploy.yml
  1. Buka file deploy.yml dan salin Gist di bawah ini ke file deploy.yml.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
name: CI-CD

on: push

jobs:
  compile-assets:
    name: Compile assets
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        # Build JS
      - name: Setup node
        uses: actions/setup-node@v1
        with:
          node-version: '12'
      - name: Install dependencies
        run: npm ci
      - name: NPM Build
        run: |
          npm run prod
          cat public/mix-manifest.json # see version in log          
      - name: Upload compile assets
        uses: actions/upload-artifact@v1
        with:
          name: assets
          path: public
  deploy:
    name: Deploy to production
    runs-on: ubuntu-latest
    needs: [compile-assets, test-php]
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v2
      - name: Download build assets
        uses: actions/download-artifact@v1
        with:
          name: assets
          path: public
      - name: Setup PHP
        uses: shivammathur/setup-php@master
        with:
          php-version: '8.1'
          extensions: mbstring, bmath
      - name: Composer install
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
      - name: Setup Deployer
        uses: atymic/deployer-php-action@master
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
      - name: Deploy to Prod
        env:
          DOTENV: ${{ secrets.DOTENV }}
        run: dep deploy production --tag=${{ env.GITHUB_REF }} -vvv

runs-on: ubuntu-latest ini menggunakan distro Ubuntu Linux milik server GitHub bukan server tempat kita menaruh proyek Laravel.

  1. Saya menggunakan Nginx sebagai web server di proyek ini. Berikut konfigurasinya.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
server {
	listen 80;
  listen [::]:80;

  server_name example.com www.example.com;

  root /var/www/example.com/current/public;
  index index.php index.html;

  # serve static files directly
	location ~* \.(jpg|jpeg|gif|css|png|js|ico|html)$ {
		access_log off;
		expires max;
		log_not_found off;
	}

	# removes trailing slashes (prevents SEO duplicate content issues)
	if (!-d $request_filename)
	{
		rewrite ^/(.+)/$ /$1 permanent;
	}

	# enforce NO www
	if ($host ~* ^www\.(.*))
	{
		set $host_without_www $1;
		rewrite ^/(.*)$ $scheme://$host_without_www/$1 permanent;
	}

	# unless the request is for a valid file (image, js, css, etc.), send to bootstrap
	if (!-e $request_filename)
	{
		rewrite ^/(.*)$ /index.php?/$1 last;
		break;
	}

	location / {
		try_files $uri $uri/ /index.php?$query_string;
	}

	# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
  location ~ \.php$ {
      include snippets/fastcgi-php.conf;
      # With php7.0-fpm:
      fastcgi_pass unix:/run/php/php7.4-fpm.sock;
      # Please read this link: 
      # https://github.com/lorisleiva/laravel-deployer/blob/919ea8386308d86837f434b69f40529854d15082/docs/troubleshooting.md#my-changes-dont-show-up-after-a-deployment
      # This link is the reason why I put the script below
      fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
      fastcgi_param DOCUMENT_ROOT $realpath_root;
  }

    location ~ /\.ht {
		deny all;
	}
}

Selamat mencoba!