Today we’re going to dive into CI/CD using GitHub Actions and Railway (https://railway.app), in the context of a Laravel and Livewire project that I’ve been building in stages on my Youtube channel.
Speaking of, you can check out the corresponding video here:
TL;DR
You can find an example Github Action here: https://github.com/GringoDotDev/stoic-podcast/blob/main/.github/workflows/run_tests.yml
You will need to create a .env.ci.example file that contains appropriate environment variables for your CI environment (there is an example in the repo).
Add those files into your project and you’re good to go!
Why CI/CD Matters
Remember the old days? I was at LinkedIn a long time ago (pre-IPO), and the testing/deployment process was neither continuous nor timely. A simple code change could often take a month or more to go live on the site, in large part because changes had to be verified manually, then verified again with all the other changes from every other team before a release.
Luckily, with all the tools we have available to us now, that’s no longer the case. With a correctly configured CI/CD pipeline, you can:
Run your test suite every time you create a change
Build your project
Deploy it to production as soon as all your tests pass
This can all happen the moment you have a new change you feel is ready for release.
But Why Test At All?
I know it’s trendy to “not write tests” because it “slows you down” and “typescript catches everything” and you “fix bugs really fast in prod anyway”, but that opinion is rather silly, and - candidly - mostly promoted by people who are a) inexperienced and/or b) trying to drive engagement on social media with their hot takes.
Any experienced developer knows that a pragmatic suite of tests will speed you up instead of slow you down.
It’s honestly a topic for another post, but even if you’re building a small project by yourself, having at least a handful of functional and/or integration tests will help you move much, much faster.
And these days, with tools like Copilot, it’s easier than ever to generate the basic scaffolding for your tests and tweak them to perfection.
Local Testing with Act
Before you drown in a sea of commits trying to perfect your GitHub action, there's Act
. It lets you run your actions locally, saving you from commit history pollution.
You can check out the project here: https://github.com/nektos/act
It will take some minutes to set up the Docker containers the first time, but after that, you can iterate quickly on your Github Actions just like you were deploying them to your Github repo.
Crafting a GitHub Action for Laravel
Here’s the code for our Github Action. Make sure to put in under .github/workflows in your project root.
name: Laravel Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
laravel-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: stoic_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Copy .env.ci.example to .env
run: cp .env.ci.example .env
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: pdo_pgsql
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-interaction
- name: Install npm dependencies
run: npm install
- name: npm build
run: npm run build
- name: Migrate database
run: php artisan migrate
- name: Run tests
run: php artisan test
You’ll notice we’re doing a few things here:
Configuring it to run whenever there’s a push or PR on the main branch
Spinning up a postgres container
Copying over an env file that contains the correct details to connect to the CI database
Installing dependencies, migrating the databae, and running the tests
You’ll want to make sure the variables in your .env.ci.example file match the DB we’re spinning up, if you follow the example above, they will look like this:
DB_CONNECTION=pgsql
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=stoic_test
DB_USERNAME=user
DB_PASSWORD=password
Integrating with Railway
That’s all well good, but we want to gate our deployments on the tests passing! In Railway, which is my preferred platform for hosting, it’s a simple setting in your project:
By enabling check suites, we’re telling Railway to hold off on deploying our code until all Github Actions have finished successfully. Meaning our tests have passed!
Final Thoughts
CI/CD is about moving faster and safer. It's about catching errors before they become fires in production.
So, try this out, adapt it to your needs, and let me know how it goes. And remember, only you can prevent production fires…with tests!