Github Actions. A simple example for a confident acquaintance
Content
Introduction
Here I will talk about my setup experience CI/CD with the help GitHub Actions.
This article will help those who want to set up automatic deployment for a personal / school project to their remote server using the free GitHub Actions services. Moreover, this service can be used for free even with a private repository (at the time of this writing).
I focus on those points that for me were not the most obvious, reading quick guide from Github.
It is assumed that you already know how to use Github. By and large, it doesn’t matter what programming language or stack you have: the main thing is to understand how Github Actions work and be able to apply it to any project.
1. Workflow files
Let’s start by creating workflow
file that Github uses to run Actions. On the tab Actions on the repository page based on your code Github offers different templates workflow
file, start with simple workflow.

After pressing the button Configure
you will see the contents of the file.
Sample basic workflow file simple.yml
name: CI
on:
# События, которые запускают jobs
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
# jobs запускаются параллельно, если не указана последовательность
jobs:
# Название job вы можете назвать как угодно
my_build_job:
# Операционная система, в которой запускаются процессы
runs-on: ubuntu-latest
# Шаги
steps:
# Actions от github: проверяет репозиторий, гит и т.д.
- uses: actions/checkout@v3
# Пример однолинейного простого скрипта shell
- name: Run a one-line script
run: echo Hello, world!
# Пример многолинейного скрипта shell
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
Reading a file: workflow
file launches jobs with the title my_build_job
on the events of sending the code to the repository and creating a Pull Request on the branch main
. my_build_job
runs on Ubuntu OS, uses action
with the title actions/checkout@v3
and performs 2 steps: write single-line and multi-line texts to the console.
After creation name-of-your-wokflow-file.yml
file, in the repository you will have a folder with the file .github/workflows/name-of-your-wokflow-file.yml
. You can create multiple workflow files.
So, the point is to start processes at certain actions on the branch you choose.
For example, “make a deploy” means we need to start the processes:
checking code with linter,
running project tests,
receiving changes in files in the project folder on a remote server, restarting some project services (containers, overwriting folders / files) on a remote server for the changes to take effect.
And if you write with workflow
file, in more detail this sequence will be as follows:
Github builds an operating system (no UI)
Checks your repository: its presence, git, necessary branches, authorization.
Copies your repository to this operating system after a successful checkout
Runs code review, tests.
Sends the changes you’ve made to your repository to your remote server.
Performs actions for your changes to take effect on your site.
Usually code review, test run and deploy divided into separate job
that run individual runners. A runner is a server that runs one job
.
2. Actions
The most interesting thing in the workflow file – actions
in line uses
. In the simplest example above, this is – actions/checkout@v3. You can see in the source code what does action
. But it’s easier to look at the progress page job
after you run it:
copies variables inside the container
checks the version
git
creates the folders you need, writes the settings filechecks out the repository
is authorized
copies the repository inside the container
goes to branch
main

There are many actions
created by developers, which can be used by selecting the desired one on Github Marketplace.
For example, in my needs I used D3rHase/ssh-command-action@v0.2.2which runs my console command via ssh on a remote server.
3. My example
My example project is created by the Ruby on Rails generator because examples without toppings are not examples. I chose a template workflow file from those offered by Github and added it to my needs.
My rough list of required actions for CI/CD. In the rubyonrails.yml file, the sequence of processes is as follows:
I checked the code with a linter.
Ran the tests.
Actions on the remote server:
Go to the project folder.
Getting changes from the repository using git-pull.
Recreation docker containers with a project
Your actions may be different. For example, if the project is a browser client application, then you need to generate final files with the command npm run build
and copy the files to a specific folder on the remote server, from which the already configured nginx. In this case, it would be suitable for copying garygrossgarten/github-action-scp@v1.0
My example .github/workflows/rubyonrails.yml
# This workflow will install a prebuilt Ruby version, install dependencies, and
# run tests and linters. Then it pulls new features from my repo and
# rebuild containers on remote server through ssh.
name: "Ruby on Rails CI"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Ruby and gems
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
with:
bundler-cache: true
- name: Lint Ruby files
run: bundle exec rubocop
test:
needs: lint
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
ports:
- "5432:5432"
env:
POSTGRES_DB: rails_test
POSTGRES_USER: rails
POSTGRES_PASSWORD: password
env:
POSTGRES_DB: rails_test
POSTGRES_USER: rails
POSTGRES_PASSWORD: password
RAILS_ENV: test
DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test"
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Ruby and gems
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
with:
bundler-cache: true
- name: Set up database schema
run: bin/rails db:schema:load
- name: Run tests
run: |
bundle exec rake db:drop db:create db:migrate db:seed;
bin/rake test;
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Ruby and gems
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
with:
bundler-cache: true
- name: Run command on remote server
uses: D3rHase/ssh-command-action@v0.2.2
with:
host: ${{secrets.SSH_HOST}}
user: ${{secrets.SSH_USER}}
private_key: ${{secrets.SSH_PRIVATE_KEY}}
command: |
cd ${{ secrets.PROJECT_FOLDER }};
git checkout main;
git pull;
docker-compose --file docker-compose.prod.yml down;
docker-compose --file docker-compose.prod.yml up -d;
docker system prune --all --force;
Workflow
the file is run when a change is made or a Pull Request is created on a branch main
. doing 3 jobs
with titles lint
, test
and deploy
Each runner runs on Ubuntu, checks the repository with actions/checkout@v3
installs Ruby and required libraries with ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
. Linter checks the code using the library rubocop
. For tests I need a database, it is started by a service postgres
with test environment variables. This step also checks the database schema, runs seeds and, in fact, the tests themselves. Step deploy
I only run on the `main` branch. Here you can see which console commands are being executed on the remote server with action
D3rHase/ssh-command-action@v0.2.2. Next, I go to the folder with the project on the remote server, go to the branch main
pull changes from repository, restart docker services, I clean the excess related to docker.
As you understand, my remote server is preconfigured with git, docker, docker-composeGithub is friends with the server using an SSH key.
Runners are executed sequentially, for this the keyword is used needs
in body job
.
Visually, the runners on Github look pretty and intuitive.

You can go into each rectangle and see in more detail what is happening there. If something goes wrong, inside you can read what didn’t work out, you can also write visual dividers in scripts for yourself or output some data, such as a list of files.
Job example at the process setup stage
- name: Run command on remote server
uses: D3rHase/ssh-command-action@v0.2.2
with:
host: ${{secrets.SSH_HOST}}
user: ${{secrets.SSH_USER}}
private_key: ${{secrets.SSH_PRIVATE_KEY}}
command: |
echo '--- START WORK ON REMOTE SERVER ---';
cd ${{ secrets.PROJECT_FOLDER }};
echo '--- LIST OF FILES ---';
ls -al;
echo '--- GIT INFORMATION ---'
git co dev;
git pull;
echo '--- DOCKER OPERATIONS ---';
docker-compose down;
echo '--- LIST OF DOCKER CONTAINERS AFTER STOPING DOCKER CONTAINERS ---';
docker ps;
docker-compose --file docker-compose.prod.yml up -d;
docker system prune --all --force;
echo '--- LIST OF DOCKER CONTAINERS AFTER STARTING DOCKER CONTAINERS ---';
docker ps;
After setting up the CI / CD process, you can remove the excess.
4.Secrets
In the rubyinrails.yml file, you may have noticed the variables that are called from the object secrets
. These variables are needed in order to make friends with your remote server with the containers created on Github for the duration of the action. To do this, I took the following steps:
Generated an SSH key on a remote server:
cd ~/.ssh; ssh-keygen -t ed25519 -C "your_email@example.com"
Let’s say I named the created files test and got 2 files – private and public keys.

I copied the contents of the private key into a variable in
SSH_PRIVATE_KEY
tab Settings -> Secrets -> Actions:Created more variables
SSH_HOST
andSSH_USER
,PROJECT_FOLDER
with relevant content.

Conclusion
Total for the article is shown:
How to create workflow.yml files that will run the actions you need.
Where and how to find what you need
actions
or write a single-line or multi-line bash script yourself if the task is simple.Where to store secret variables that you can use in your
workflow
file.How to debug in case of errors.
Enjoy programming!