---
layout: "@/layouts/BlogPostLayout.astro"
title: "Creating Automated Deployment Workflows with Gitea Actions"
description: "Using a frontend project deployment as an example, this article provides a step-by-step guide on how to create automated deployment workflows using Gitea actions. For those who don't want to use Jenkins, Gitea actions is a good CI/CD solution."
date: "2023-12-15"
image: "https://images.unsplash.com/photo-1607799279861-4dd421887fb3?q=80&w=1470&auto=format&fit=crop"
tags: ["Gitea", "Actions", "CI/CD", "Automated Deployment"]
tagId: ["gitea", "actions", "cicd", "automation-deployment"]
category: "DevOps"
categoryId: "devops"
readTime: "10 min read"
---
Using a frontend project deployment as an example, this article will provide a step-by-step guide on how to create automated deployment workflows using Gitea actions. For those who don't want to use Jenkins, Gitea actions is a good `CI/CD` solution.

<!-- truncate -->

## Introduction
After developing a project, we need to deploy it to a server so that users can access it. However, manual deployment for each update wastes time and energy. Therefore, we can use Gitea actions to create an automated workflow for quick project deployment.

## What is Gitea Actions
Gitea actions is a feature of Gitea that allows us to create automated workflows for quick project deployment. Gitea actions can perform various tasks such as building, testing, and deploying. Gitea actions can use various programming languages and tools, such as Python, Java, Docker, etc. Additionally, Gitea actions is configured to be compatible with GitHub actions configuration as much as possible, so if you are already familiar with GitHub actions, you can also easily use Gitea actions.
For more details, see the official documentation: [Gitea actions](https://docs.gitea.com/en/usage/actions/overview)

## Preparation
Before we start creating this workflow, you need to prepare the following materials:
1. A server, preferably with root privileges (for installing tools)
2. A deployed Gitea repository, version at least >= `v1.19`, officially strongly recommended to use `v1.20` or higher
3. Docker environment (not required, used on the host where the runner is deployed)

> Note: The author has prepared: one build server + one deployment (target) server. You can use just one server, but be mindful of environment isolation.

Please ensure you have completed all preparations before proceeding with subsequent steps. For specific installation and configuration, please refer to the official documentation, which I won't repeat here. [Installation](https://docs.gitea.com/en/category/installation)

If you use the **1panel panel**, you can complete the installation with one click from the app store, which is what the author used.

>Note: In versions < `v1.21.0`, you need to enable the `actions` feature in the configuration file. Please refer to the official documentation [Configuration](https://docs.gitea.com/en/usage/actions/quickstart). In later versions, it is enabled by default.

This tutorial uses Gitea version `v1.22.6`!


## Configuring Runner
In Gitea actions, running Jobs is done through Runners, so we need to configure a Runner first for our subsequent deployment workflow. There are two installation methods: you can install directly on the host or through a docker container. Here, the author will install through docker. To avoid excessive resource consumption, you can install it on a separate host (PS: For users in China, you can choose a Hong Kong server, which will make running jobs faster 😄). The author chose a Hong Kong VPS as the host.

### Downloading act_Runner

First, we need to download this tool to register or generate configurations [Download Link](https://gitea.com/gitea/act_runner/releases). Please select the version corresponding to your system on this page for download. After downloading, upload it to the server. Here, I uploaded it to the `/gitea_runner2` directory on the server. You can also use tools like `curl` to download directly. You can use the following command, but pay attention to the name and download link. My system is `Debian12 x86_64`, so I downloaded `act_runner-0.2.11-linux-amd64`.

```bash
curl -o act_runner2 https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
```
After completion, set the execution permissions
```bash
chmod +x act_runner2 # Note the filename, please modify according to your actual situation

./act_runner --version # Seeing the version information means success
```
### Generating Configuration
```
./act_runner2 generate-config > config.yaml
```
Generally, the default configuration is sufficient. If you have other requirements, please modify `config.yaml` yourself.

**Since the author chose to install via docker, if you choose to install directly on the host, you can skip the subsequent installation steps and refer to the official documentation for installation [Direct Installation](https://docs.gitea.com/en/usage/actions/quickstart)**

### Registering Runner

Here I choose to register through the `docker-compose.yml` configuration method

**Note: Modify the parameters in the configuration according to your own situation. The following configuration is for reference only!!!**

```yaml
version: "3.8"
services:
  runner:
    image: gitea/act_runner:latest
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: "<your_gitea.com>"
      GITEA_RUNNER_REGISTRATION_TOKEN: "<your_registration_token_here>"
      GITEA_RUNNER_NAME: "act_vps_runner2"
      GITEA_RUNNER_LABELS: "act_vps_runner2"
    volumes:
      - /gitea_runner2/config.yaml:/config.yaml
      - /gitea_runner2/runner_data/data:/data
      - /gitea_runner2/runner_data/cache:/root/.cache
      - /var/run/docker.sock:/var/run/docker.sock
```
### Configuration Explanation

**GITEA_RUNNER_REGISTRATION_TOKEN** This parameter is the registration token, which needs to be obtained from your gitea. There are three levels in total. I directly chose the organization level, which can be obtained from the organization settings page, as shown in the following figure:
![image](https://pic1.imgdb.cn/item/6779f8dfd0e0a243d4ef14ab.png)

For other instance configuration pages and explanations, please refer to the official documentation, which has detailed explanations.

**If you cannot see the settings page, please ensure that you have the correct permissions and that Actions is enabled. Check the Gitea version!!!**

### Running and Generating Runner

Since the author uses the 1panel panel, it can be directly orchestrated in the container options. If you don't use this type of visualization tool, you need to manually execute `docker-compose up -d`

After success, you can check the container's operation and logs. The logs will display the registration success information, as shown in the following figures:

![image](https://pic1.imgdb.cn/item/677893b5d0e0a243d4eeccca.png)
![image](https://pic1.imgdb.cn/item/677893bad0e0a243d4eecccb.png)

Then we return to the page where you obtained the runner token, refresh it, and you should see your runner, as shown in the following figure:
![image](https://pic1.imgdb.cn/item/6779f844d0e0a243d4ef1478.png)
The name in the figure is based on the name you set during registration configuration.

> At this point, our runner deployment is complete, and we can now use it in projects.

## Using Actions

The usage of gitea actions is similar to github actions. If you are not familiar with this type of configuration, you can first learn about **github actions**.
The author will use a `vitepress` project as an example.

### Creating Configuration Files
1. Create a `.gitea/workflows` directory in the project root directory
2. Create a `deploy.yaml` configuration file in this directory
3. Let's first write a test configuration to test environment variables, as shown below:
```yaml
name: Deploy Test

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Print Environment Variables
        run: env

      - name: Print Current Directory
        run: pwd

      - name: List Files in Working Directory
        run: ls -la
```
Then commit the code to the repository, and we should be able to see the execution task in the repository's actions, as shown in the following figure:
![image](https://pic1.imgdb.cn/item/6779f972d0e0a243d4ef14da.png)

> **PS: If you don't see actions execution, or even don't see the actions option, you may need to enable the actions option in the repository, organization, or management page**
### Configuring Build
We modify the `deploy.yaml` configuration file to add build steps. I'm using pnpm here, please adjust some configurations according to your actual situation. I won't elaborate too much here.

````yaml
name: Deploy docs for project
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]
    steps:
      # TODO Temporary solution for https://github.com/go-gitea/gitea/issues/32481, waiting for gitea to release 1.23.0 for fix
      - name: Install git
        run: |
          if ! command -v git &> /dev/null; then
            apt-get update && apt-get install -y git
          else
            echo "git is already installed"
          fi

      - name: Checkout repository code
        uses: https://gitea.com/actions/checkout@v4
        with:
          ref: 'main'

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      # Skip pnpm cache to reduce overhead
      - name: Install pnpm
        run: |
          if ! command -v pnpm &> /dev/null; then
            npm install -g pnpm@9
          else
            echo "pnpm is already installed"
          fi

      - name: Check Node.js and pnpm versions
        run: |
          echo "pnpm version ===>" && pnpm -v
          echo "node version ===>" && node -v

      # Simplify node_modules handling without caching
      - name: Install dependencies
        run: |
          if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
            echo "Dependencies are outdated or missing, installing..."
            pnpm install
          else
            echo "Dependencies are up-to-date, skipping installation."
          fi

      - name: Build docs
        run: pnpm run docs:build
````

> **Special note: Installing git is to fix the current issue in gitea actions where code cannot be checked out through `actions/checkout`. This issue is expected to be fixed in `v1.23.0`. Currently, we can only handle it this way. But from the author's observation, it seems to be related to the image version used. If it's `ubuntu-latest`, you might not need to install it. Please base it on your actual situation.**

If everything goes smoothly, you should see the build task completed in the workflow, as shown in the figure:
![image](https://pic1.imgdb.cn/item/677bcf9bd0e0a243d4f00c7a.png)

### Deploying the Project

The principle of code deployment is actually to connect to the target server via `ssh` and execute deployment scripts. Therefore, we need to configure SSH first, then use the `actions` [easingthemes/ssh-deploy](https://github.com/easingthemes/ssh-deploy) to connect to the target server and push the code.

#### Configuring SSH
This step is actually to configure a user for the server and support `SSH` login, which is password-free login. Therefore, we use the following commands in sequence on the **target server**. The author is using `Debian 12`, please adjust the commands according to your actual situation!

##### Creating a User (It is recommended to create a user to operate the deployment directory, do not use the root user!!!)
```bash
sudo adduser deployuser # Please modify the username according to your actual situation
```
After successful creation, you can check if the `/home/deployuser` directory exists. If it doesn't exist, you may need to create it manually.

##### Creating Key Pairs
We need to first switch to the `deployuser` user, then execute the following commands:
```bash
# 1. Create public and private keys
ssh-keygen -t rsa -b 4096 -C ""
# Add the public key content to the authorized_keys file
echo ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
# Set correct permissions
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
```
##### Granting Read and Write Permissions to Specific Directories (Can be skipped)
This requires a user with `root` privileges to operate:
```bash
# 1. Create directory
mkdir -p /var/www/html/project
# 2. Modify directory permissions
sudo chown deployuser:deployuser /var/www/project
sudo chmod 700 /var/www/project
```
Since the author used `1panel` to manage websites, the directory is automatically generated, which differs from this, but the process steps are the same. Please modify according to your actual situation.

#### Configuring Key Information in Gitea

1. In your gitea's personal, organization, or management actions page, click `Secrets`, copy the private key we just generated on the server to the `Secret`, and then click `Add Secret`.
2. Since we will need the server IP and username later, it is also recommended to configure them in secrets or variables for later use.

Here, the author has configured the private key, server IP, and username in secrets, as shown in the figure:
![image](https://pic1.imgdb.cn/item/677bd9c0d0e0a243d4f011f2.png)

At this point, we have completed the configuration required for deploying the project, and we can now start the deployment process.

> PS: Since `easingthemes/ssh-deploy` needs to use `rsync` to execute script commands, this tool is required in both the image container and the target server. If it doesn't exist, you need to install it in advance, especially on the **target server**. Generally, this needs to be done by a root user or a user with installation permissions.

### Project Deployment

We add the following code to `deploy.yml`, pay attention to the format!!!

````yaml
      - name: ssh deploy
        uses: easingthemes/ssh-deploy@v5.1.0
        with:
          SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
          REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
          SOURCE: "/.vitepress/dist/"
          TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # Please modify to your deployment directory according to your actual situation
          EXCLUDE: "/node_modules/"
````
If everything goes smoothly, you will see the actions executed successfully, and there will be a dist directory in your deployment directory, as shown in the following figures:
![image](https://pic1.imgdb.cn/item/677bdd23d0e0a243d4f014a3.png)
![image](https://pic1.imgdb.cn/item/677bdd1bd0e0a243d4f014a1.png)

Next, you just need to use tools like `nginx` to point to this directory, and it can be accessed.

> PS: If you encounter errors in the above step, please carefully check the error logs. Based on the author's experience, it's most likely due to issues with SSH keys and permission handling. If you're not using `ubuntu-latest`, you may also need to install some necessary tools according to the error messages, such as `git`, `rsync`, etc.


### Complete Configuration

````yaml
name: Deploy docs for project
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]
    steps:
      - name: Checkout repository code
        uses: https://gitea.com/actions/checkout@v4
        with:
          ref: 'main'

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      # Skip pnpm cache to reduce overhead
      - name: Install pnpm
        run: |
          if ! command -v pnpm &> /dev/null; then
            npm install -g pnpm@9
          else
            echo "pnpm is already installed"
          fi

      - name: Check Node.js and pnpm versions
        run: |
          echo "pnpm version ===>" && pnpm -v
          echo "node version ===>" && node -v

      # Simplify node_modules handling without caching
      - name: Install dependencies
        run: |
          if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
            echo "Dependencies are outdated or missing, installing..."
            pnpm install
          else
            echo "Dependencies are up-to-date, skipping installation."
          fi

      - name: Build docs
        run: pnpm run docs:build

      - name: ssh deploy
        uses: easingthemes/ssh-deploy@v5.1.0
        with:
          SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
          REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
          SOURCE: "/.vitepress/dist/"
          TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # Please modify to your deployment directory according to your actual situation
          EXCLUDE: "/node_modules/"

````