---
tags:
  - GIT
  - CI/CD
  - CI-CD
---
🚀 Part 2: Automatically Deploy to a Live Server

The safest, cleanest way to deploy to a remote homelab server from a runner is using **SSH and Docker Contexts**. This allows your Gitea Actions runner to securely command your live server's Docker daemon across your local network without needing Git installed on the destination server.

1. Generate a Deployment SSH Key [[1](https://ecostack.dev/posts/automated-docker-compose-deployment-github-actions/)]

On your local machine or server, generate a dedicated SSH key pair for Gitea Actions. **Do not use a passphrase.**

ssh-keygen -t ed25519 -f ~/.ssh/gitea_deploy_key -N ""


- Append the contents of `gitea_deploy_key.pub` to the `~/.ssh/authorized_keys` file on your **live deployment server**.
- Copy the contents of the private key `gitea_deploy_key`. Save it in your Gitea Repository Secrets as **`DEPLOY_SSH_KEY`**.
- Save your live server's local IP address or local hostname in Gitea Secrets as **`DEPLOY_HOST`**.
- Save your live server's SSH username in Gitea Secrets as **`DEPLOY_USER`**.

2. Create the Complete Production Workflow

Create or update your `.gitea/workflows/deploy.yaml` file. This workflow will validate your code first, and then deploy it only if the branch is `main`.

name: Validate and Deploy Homelab

on:
  push:
    branches: [ "main" ]

jobs:
  test-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout Repository Code
        uses: actions/checkout@v4

      - name: Verify Compose File Validity
        env:
          DB_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
          DB_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
        run: docker compose config

      # --- DEPLOYMENT PHASE ---
      
      - name: Set up SSH Private Key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          # Scan the host key to prevent SSH hanging on a manual confirmation prompt
          ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts

      - name: Create Remote Docker Context
        run: |
          # Define a remote endpoint pointing to your live server over SSH
          docker context create homelab-target \
            --docker "host=ssh://${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}"

      - name: Deploy Containers to Live Server
        env:
          # Pass your production secrets to the live deployment step
          DB_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
          DB_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
        run: |
          echo "Switching Docker context to live server..."
          docker context use homelab-target
          
          echo "Pulling latest images and starting services remotely..."
          # The --project-directory . flag ensures it reads the docker-compose file fetched by Git
          docker compose --project-directory . up -d --remove-orphans


💡 Why This Setup Wins for Homelabs

- **Zero Overhead:** You do not need to install complex deployment tools or agents on your production server. It only requires standard SSH and Docker.
- **Security:** If you ever need to revoke the runner's deployment access, you simply delete the single public key from your live server's `authorized_keys` file.
- **Atomic Changes:** The `--remove-orphans` flag cleans up old containers that you removed from your Compose file since the last Git commit, keeping your server immaculate.