Add CI/CD pipeline and Docker deployment #8

Closed
opened 2026-03-11 15:24:25 +01:00 by dostulata · 0 comments
Owner

Summary

Set up a CI/CD pipeline using Gitea Actions and Docker to build, test, and deploy the Initiative app to a VPS at https://initiative.dostulata.rocks. The workflow runs quality checks on every push and builds/publishes a Docker image on semver tags. The VPS serves the static SPA behind Caddy (automatic HTTPS).

Acceptance Criteria

  • Dockerfile produces a working container that serves the SPA
  • Gitea Actions workflow runs pnpm check on every push
  • Workflow builds and pushes a Docker image to the Gitea registry on semver tags
  • VPS serves the app at https://initiative.dostulata.rocks with automatic HTTPS
  • A setup guide documents all manual VPS steps (runner, Caddy, Docker)
VPS Setup Guide

VPS Setup Guide — Initiative

This guide walks you through setting up a VPS to host the Initiative app at initiative.dostulata.rocks.

1. Get a VPS

Recommended: Hetzner Cloud CX22 (~4 EUR/mo) — 2 vCPU, 4GB RAM, 40GB disk.

Any provider works (DigitalOcean, Contabo, Netcup, etc). Requirements:

  • Ubuntu 24.04 LTS (or Debian 12)
  • Public IPv4 address
  • SSH access

2. Point your domain

In your domain registrar's DNS settings, create:

Type: A
Name: @               (or leave blank, depending on registrar)
Value: <YOUR_VPS_IP>
TTL: 300

If you also want www.dostulata.rocks:

Type: CNAME
Name: www
Value: dostulata.rocks
TTL: 300

Verify propagation (may take a few minutes):

dig +short dostulata.rocks
# Should return your VPS IP

3. Initial VPS setup

SSH into your VPS:

ssh root@<YOUR_VPS_IP>

3.1 Update system and install Docker

apt update && apt upgrade -y

# Install Docker (official method)
curl -fsSL https://get.docker.com | sh

# Verify
docker --version
docker compose version
adduser deploy
usermod -aG docker deploy

Then log in as deploy for the remaining steps, or continue as root.

4. Set up Caddy (reverse proxy + automatic HTTPS)

Caddy runs directly on the VPS (not in Docker) and proxies requests to your app container. It handles HTTPS certificates from Let's Encrypt automatically.

4.1 Install Caddy

apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy

4.2 Configure Caddy

cat > /etc/caddy/Caddyfile << 'EOF'
dostulata.rocks {
	reverse_proxy localhost:8080
}
EOF

That's it. Caddy will automatically obtain and renew HTTPS certificates.

4.3 Start Caddy

systemctl enable caddy
systemctl restart caddy

5. Set up Gitea Actions Runner

The runner executes your CI/CD workflows on this VPS.

5.1 Download act_runner

Check the latest version at https://gitea.com/gitea/act_runner/releases, then:

# Replace VERSION with the latest (e.g. 0.2.11)
VERSION=0.2.11
curl -L -o /usr/local/bin/act_runner \
  "https://gitea.com/gitea/act_runner/releases/download/v${VERSION}/act_runner-${VERSION}-linux-amd64"
chmod +x /usr/local/bin/act_runner

5.2 Get a registration token

  1. Go to https://git.bahamut.nitrix.one/dostulata/initiative/settings/actions/runners
  2. Click "Create new Runner"
  3. Copy the registration token

5.3 Register the runner

act_runner register \
  --instance https://git.bahamut.nitrix.one \
  --token <YOUR_REGISTRATION_TOKEN> \
  --name initiative-runner \
  --labels ubuntu-latest:docker://node:22

The --labels flag maps the ubuntu-latest label (used in workflows) to a Docker image the runner will use.

5.4 Run as a systemd service

cat > /etc/systemd/system/act_runner.service << 'EOF'
[Unit]
Description=Gitea Actions Runner
After=docker.service

[Service]
Type=simple
User=root
WorkingDirectory=/root
ExecStart=/usr/local/bin/act_runner daemon
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable act_runner
systemctl start act_runner

Verify it's running:

systemctl status act_runner

You should also see the runner appear as "Online" in your Gitea repo's runner settings.

6. Set up the app container

6.1 Create an app directory

mkdir -p /opt/initiative

6.2 Create docker-compose.yml

cat > /opt/initiative/docker-compose.yml << 'EOF'
services:
  web:
    image: git.bahamut.nitrix.one/dostulata/initiative:latest
    restart: unless-stopped
    ports:
      - "8080:80"
EOF

This maps port 8080 (what Caddy proxies to) to port 80 inside the container (where Nginx serves the static files).

6.3 Log in to the Gitea container registry

docker login git.bahamut.nitrix.one
# Username: your Gitea username
# Password: a personal access token with package:read scope

To create a token: Gitea > Settings > Applications > Generate New Token (enable read:package).

6.4 First deploy (once the CI has pushed an image)

cd /opt/initiative
docker compose pull
docker compose up -d

7. Deploy new versions

After the CI builds and pushes a new image (triggered by a git tag), update the running container:

cd /opt/initiative
docker compose pull
docker compose up -d

Optional: Auto-deploy with Watchtower

To automatically pull and restart when a new latest image is pushed:

mkdir -p /opt/watchtower
cat > /opt/watchtower/docker-compose.yml << 'EOF'
services:
  watchtower:
    image: containrrr/watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /root/.docker/config.json:/config.json:ro
    command: --interval 300 --cleanup
EOF

cd /opt/watchtower
docker compose up -d

Watchtower checks every 5 minutes for new images and auto-deploys.

8. Verify everything works

  1. Push a semver tag to trigger the CI:
    git tag 0.1.0
    git push origin 0.1.0
    
  2. Watch the workflow run at https://git.bahamut.nitrix.one/dostulata/initiative/actions
  3. Once it completes, the image is in the registry
  4. Pull and start on the VPS (or Watchtower does it automatically)
  5. Visit https://initiative.dostulata.rocks — you should see your app with HTTPS

Troubleshooting

Caddy not getting certificates:

  • Make sure ports 80 and 443 are open in your VPS firewall
  • Make sure DNS is pointing to the VPS: dig +short dostulata.rocks
  • Check logs: journalctl -u caddy

Runner not picking up jobs:

  • Check status: systemctl status act_runner
  • Check Gitea: repo Settings > Actions > Runners (should show "Online")
  • Check logs: journalctl -u act_runner

Docker pull failing:

  • Make sure you're logged in: docker login git.bahamut.nitrix.one
  • Check token has read:package scope

Container not starting:

  • Check logs: cd /opt/initiative && docker compose logs
## Summary Set up a CI/CD pipeline using Gitea Actions and Docker to build, test, and deploy the Initiative app to a VPS at https://initiative.dostulata.rocks. The workflow runs quality checks on every push and builds/publishes a Docker image on semver tags. The VPS serves the static SPA behind Caddy (automatic HTTPS). ## Acceptance Criteria - [x] Dockerfile produces a working container that serves the SPA - [x] Gitea Actions workflow runs `pnpm check` on every push - [x] Workflow builds and pushes a Docker image to the Gitea registry on semver tags - [x] VPS serves the app at https://initiative.dostulata.rocks with automatic HTTPS - [x] A setup guide documents all manual VPS steps (runner, Caddy, Docker) <details> <summary>VPS Setup Guide</summary> # VPS Setup Guide — Initiative This guide walks you through setting up a VPS to host the Initiative app at `initiative.dostulata.rocks`. ## 1. Get a VPS Recommended: **Hetzner Cloud CX22** (~4 EUR/mo) — 2 vCPU, 4GB RAM, 40GB disk. Any provider works (DigitalOcean, Contabo, Netcup, etc). Requirements: - Ubuntu 24.04 LTS (or Debian 12) - Public IPv4 address - SSH access ## 2. Point your domain In your domain registrar's DNS settings, create: ``` Type: A Name: @ (or leave blank, depending on registrar) Value: <YOUR_VPS_IP> TTL: 300 ``` If you also want `www.dostulata.rocks`: ``` Type: CNAME Name: www Value: dostulata.rocks TTL: 300 ``` Verify propagation (may take a few minutes): ```bash dig +short dostulata.rocks # Should return your VPS IP ``` ## 3. Initial VPS setup SSH into your VPS: ```bash ssh root@<YOUR_VPS_IP> ``` ### 3.1 Update system and install Docker ```bash apt update && apt upgrade -y # Install Docker (official method) curl -fsSL https://get.docker.com | sh # Verify docker --version docker compose version ``` ### 3.2 Create a non-root user (optional but recommended) ```bash adduser deploy usermod -aG docker deploy ``` Then log in as `deploy` for the remaining steps, or continue as root. ## 4. Set up Caddy (reverse proxy + automatic HTTPS) Caddy runs directly on the VPS (not in Docker) and proxies requests to your app container. It handles HTTPS certificates from Let's Encrypt automatically. ### 4.1 Install Caddy ```bash apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list apt update apt install caddy ``` ### 4.2 Configure Caddy ```bash cat > /etc/caddy/Caddyfile << 'EOF' dostulata.rocks { reverse_proxy localhost:8080 } EOF ``` That's it. Caddy will automatically obtain and renew HTTPS certificates. ### 4.3 Start Caddy ```bash systemctl enable caddy systemctl restart caddy ``` ## 5. Set up Gitea Actions Runner The runner executes your CI/CD workflows on this VPS. ### 5.1 Download act_runner Check the latest version at https://gitea.com/gitea/act_runner/releases, then: ```bash # Replace VERSION with the latest (e.g. 0.2.11) VERSION=0.2.11 curl -L -o /usr/local/bin/act_runner \ "https://gitea.com/gitea/act_runner/releases/download/v${VERSION}/act_runner-${VERSION}-linux-amd64" chmod +x /usr/local/bin/act_runner ``` ### 5.2 Get a registration token 1. Go to `https://git.bahamut.nitrix.one/dostulata/initiative/settings/actions/runners` 2. Click "Create new Runner" 3. Copy the registration token ### 5.3 Register the runner ```bash act_runner register \ --instance https://git.bahamut.nitrix.one \ --token <YOUR_REGISTRATION_TOKEN> \ --name initiative-runner \ --labels ubuntu-latest:docker://node:22 ``` The `--labels` flag maps the `ubuntu-latest` label (used in workflows) to a Docker image the runner will use. ### 5.4 Run as a systemd service ```bash cat > /etc/systemd/system/act_runner.service << 'EOF' [Unit] Description=Gitea Actions Runner After=docker.service [Service] Type=simple User=root WorkingDirectory=/root ExecStart=/usr/local/bin/act_runner daemon Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable act_runner systemctl start act_runner ``` Verify it's running: ```bash systemctl status act_runner ``` You should also see the runner appear as "Online" in your Gitea repo's runner settings. ## 6. Set up the app container ### 6.1 Create an app directory ```bash mkdir -p /opt/initiative ``` ### 6.2 Create docker-compose.yml ```bash cat > /opt/initiative/docker-compose.yml << 'EOF' services: web: image: git.bahamut.nitrix.one/dostulata/initiative:latest restart: unless-stopped ports: - "8080:80" EOF ``` This maps port 8080 (what Caddy proxies to) to port 80 inside the container (where Nginx serves the static files). ### 6.3 Log in to the Gitea container registry ```bash docker login git.bahamut.nitrix.one # Username: your Gitea username # Password: a personal access token with package:read scope ``` To create a token: Gitea > Settings > Applications > Generate New Token (enable `read:package`). ### 6.4 First deploy (once the CI has pushed an image) ```bash cd /opt/initiative docker compose pull docker compose up -d ``` ## 7. Deploy new versions After the CI builds and pushes a new image (triggered by a git tag), update the running container: ```bash cd /opt/initiative docker compose pull docker compose up -d ``` ### Optional: Auto-deploy with Watchtower To automatically pull and restart when a new `latest` image is pushed: ```bash mkdir -p /opt/watchtower cat > /opt/watchtower/docker-compose.yml << 'EOF' services: watchtower: image: containrrr/watchtower restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock - /root/.docker/config.json:/config.json:ro command: --interval 300 --cleanup EOF cd /opt/watchtower docker compose up -d ``` Watchtower checks every 5 minutes for new images and auto-deploys. ## 8. Verify everything works 1. Push a semver tag to trigger the CI: ```bash git tag 0.1.0 git push origin 0.1.0 ``` 2. Watch the workflow run at `https://git.bahamut.nitrix.one/dostulata/initiative/actions` 3. Once it completes, the image is in the registry 4. Pull and start on the VPS (or Watchtower does it automatically) 5. Visit `https://initiative.dostulata.rocks` — you should see your app with HTTPS ## Troubleshooting **Caddy not getting certificates:** - Make sure ports 80 and 443 are open in your VPS firewall - Make sure DNS is pointing to the VPS: `dig +short dostulata.rocks` - Check logs: `journalctl -u caddy` **Runner not picking up jobs:** - Check status: `systemctl status act_runner` - Check Gitea: repo Settings > Actions > Runners (should show "Online") - Check logs: `journalctl -u act_runner` **Docker pull failing:** - Make sure you're logged in: `docker login git.bahamut.nitrix.one` - Check token has `read:package` scope **Container not starting:** - Check logs: `cd /opt/initiative && docker compose logs` </details>
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dostulata/initiative#8