Simple automated deployment of an Astro site with Codeberg

My CI/CD pipeline to deploy this Astro site using Codeberg.

This post is a quick memo about setting up an automated deployment pipeline of an Astro (or any other TypeScript/JavaScript app) website on a VPS using a Codeberg runner installed on the VPS.

The pipeline will look like this:

Laptop

    │ git push

Codeberg


Forgejo Runner (VPS)

    ├── npm ci
    ├── npm run build
    └── rsync dist/ → /var/www/blog


Caddy


Visitors

Install Forgejo runner on the VPS (Rocky Linux)

Codeberg uses Forgejo, so the first task is to install the Forgejo runner on the server. The runner listens for pushes to main branch from the Codeberg repository and executes the defined actions.

  1. Create a dedicated user
sudo useradd --system --create-home --shell /bin/bash forgejo-runner
  • —system : Create a system user (no password, no interactive login by default).
  • —create-home : Create the home directory /home/forgejo-runner.
  • —shell /bin/bash : Bash shell.
  1. Login as the new user
sudo su - forgejo-runner

The flag - loads the environment of the user (variables HOME, PATH, etc.).

  1. Dowload of the binary from Codeberg

Get the lastest version of the runner binary from Codeberg - Forgejo Runner Releases. Example for the current latest version as of 20260720:

cd ~
mkdir -p actions-runner && cd actions-runner
wget https://code.forgejo.org/forgejo/runner/releases/download/v12.13.1/forgejo-runner-12.13.1-linux-amd64
chmod +x forgejo-runner-12.13.1-linux-amd64
ln -s forgejo-runner-12.13.1-linux-amd64 act_runner

Configure and test the runner

Before configuring the runner, register a new runner with Codeberg. On Codeberg, navigate to:

Repository
  → Settings
  → Actions
  → Runners
  → Create new runner

After registering, you’ll receive a token and a UUID. Use these in the configuration file below.

Create a config.yaml file in ~/actions-runner :

server:
  connections:
    forgejo:
      url: https://codeberg.org/
      uuid: aaaa-bbbb-cccc-dddd-111222333
      token: 731cd04efbe70d237cb0be70d237cb0be70d2
      labels:
        - linux:host
        - self-hosted:host

For a simple site deployment, a Docker-based runner isn’t necessary. Instead, we use rsync to copy the built files to the website’s root directory. The :host label suffix tells the runner to execute tasks directly on the VPS rather than in a Docker container.

Launch the runner (mode daemon) in test mode:

./act_runner daemon --config config.yaml

If everything runs without errors, proceed to make it permanent.

Create a proper systemd service

To ensure the runner starts automatically at boot, create a systemd service file at /etc/systemd/system/forgejo-runner.service:

[Unit]
Description=Forgejo Actions Runner
After=network.target

[Service]
User=forgejo-runner
WorkingDirectory=/home/forgejo-runner/actions-runner
Environment="PATH=/home/forgejo-runner/.nvm/versions/node/v22.23.1/bin:/usr/local/bin:/usr/bin:/bin"
ExecStart=/home/forgejo-runner/actions-runner/forgejo-runner-12.13.1-linux-amd64 daemon --config /home/forgejo-runner/actions-runner/config.yaml
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

Activate and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now forgejo-runner

# Check the status
sudo systemctl status forgejo-runner

# In case of any problems, check the logs
journalctl -u forgejo-runner -f

Note: SELinux may cause issues. Apply the correct context to the runner binary with:

sudo chcon -t bin_t /home/forgejo-runner/actions-runner/forgejo-runner-12.13.1-linux-amd64
  • bin_t is the context for executable binaries.

The runner should no appear as online in Codeberg (under Settings > Runners).

Create the site (Astro here) workflow

Add a .forgejo/workflows directory at the root of the project and create a YAML file named deploy.yml with the following content:

name: Deploy Astro site

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: self-hosted

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build Astro site
        run: npm run build

      - name: Deploy static files
        run: |
          rsync -av --delete dist/ /var/www/blog/

For every push to the main branch, the pipeline automatically executes:

✓ Checkout repository
✓ Setup Node.js
✓ Install dependencies
✓ Build Astro site
✓ Deploy static files

Change ownership of the Caddy root directory

The Caddy root directory /var/www/blog must be accessible by the forgejo-runner user to allow file copying:

sudo chown -R forgejo-runner:forgejo-runner /var/www/blog/

Testing the pipeline

Make a change to the site, commit and push to main:

git add .
git commit -m "Add deployment workflow"
git push

In Codeberg, navigate to Repository > Actions > Running jobs to monitor the deployment status.


More like this