Skip to content
MeshLab
All posts

.NET Application Linux SSH Deployment Using Gitlab CI/CD & Github Actions

Mesh
Updated Jan 7, 2024, 08:08 PM

Setup

  • Create a .NET 8 application, call it anything you like (CiDemo in my example):
    md CiDemo && cd CiDemo
    dotnet new sln
    dotnet new web -o src/Api
    dotnet sln add .\src\Api\Api.csproj
  • Ensure .NET 8 runtime is installed on the VM.
  • Create a systemd service on the VM sudo nano /etc/systemd/system/demo-app.service, and paste the following:
    [Unit]
    Description=Example .NET Web API App running on Linux
    
    [Service]
    WorkingDirectory=/var/www/demo-app
    ExecStart=/usr/bin/dotnet /var/www/demo-app/Api.dll
    Restart=always
    # Restart service after 10 seconds if the dotnet service crashes:
    RestartSec=10
    KillSignal=SIGINT
    SyslogIdentifier=dotnet-example
    User=www-data
    Environment=ASPNETCORE_ENVIRONMENT=Development
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    Environment=ASPNETCORE_URLS=http://localhost:5001
    
    [Install]
    WantedBy=multi-user.target
  • Enable the Service: sudo systemctl enable demo-app.service
  • At this point you can install whatever reverse proxy that suits you (nginx, apache, caddy etc..), and configure it to proxy requests to http://localhost:5001, which we specified in the systemd service environment variables.
  • Create the Build Folder: Run md /var/www/demo-app

SSH Setup

  • Generate a New Key Pair on Your Local Machine(linux/mac/wsl preferred here for later purposes): You can use ssh-keygen -t rsa -b 4096 -C "[email protected]" (no passphrase)
  • Transfer Public Key to VM: Copy the public key (id_rsa.pub or similar) to your VM and append it to the ~/.ssh/authorized_keys file. Ensure you're copying the public key, not the private one. You can do this using ssh-copy-id if it's available: ssh-copy-id -i /path/to/public/key username@vm_ip_address, or, manually copy the content of your public key file and paste it into the authorized_keys file on the VM.
  • Test SSH Connection: Using your newly generated private key, try to SSH into your VM: ssh -i /path/to/new/private/key username@vm_ip_address
  • Ensure Correct Permissions: On the VM, make sure the ~/.ssh directory has 700 permissions and the ~/.ssh/authorized_keys file has 600 permissions. You can set these permissions with the following commands:
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
  • Configure Sudo Permissions on the Target Machine: Ensure the user (let's call them ci-user) used for SSH in your CI/CD pipeline has the necessary sudo privileges. You have two main options:
    • Passwordless Sudo for Specific Commands: This is more secure than giving blanket passwordless sudo access. Edit the sudoers file using sudo visudo and add a line like: ci-user ALL=(ALL) NOPASSWD: /path/to/specific/command. Replace /path/to/specific/command with the command you want to allow. This allows ci-user to run this command with sudo without being prompted for a password.
    • Passwordless Sudo for All Commands: This is less secure and should be used cautiously. Add the following line to give ci-user passwordless sudo access for all commands: ci-user ALL=(ALL) NOPASSWD: ALL
  • Base64 Encode Your Private Key: Either encode the private key file directly to a .txt file(preferred): cat ~/.ssh/my_key | base64 -w0 > ~/encoded_key.txt, or, copy the content of your private key (the one the doesn't end with .pub) and execute the following cat PRIVATE_KEY_CONTENT | base64 -w0 > ~/encoded_key.txt
  • Next: Choose your CI/CD tool.

Github Actions:

  • Copy the contents of ~/encoded_key.txt

  • Go to Your repo > Settings > Secrets and Variables > Actions > New repository secret.

  • Give it a name, eg: DEPLOY_PRIVATE_KEY, and paste the content of ~/encoded_key.txt to the "Secret" text area.

    note: you should also create secrets and variable for your VM IP address and ssh username, which we named "ci-user" earlier in the sudo permissions step, but I'll keep it simple for now.

  • In the root directory of your code, create the following folders: .github/workflows

  • Inside the .github/workflows folder create a YAML file, call it deploy.yml for example.

  • Paste the following into the YAML file:

    name: CI/CD Pipeline

on: push: branches: - dev paths: - src/**

jobs: build: runs-on: ubuntu-latest

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

  - name: "Setup .NET"
    uses: actions/setup-dotnet@v3
    with:
      dotnet-version: "8.0.x"

  - name: Build and Publish
    run: |
      dotnet restore ApiDemo.sln
      dotnet publish src/Api/Api.csproj --no-restore --configuration Release -o published

  - name: Setup SSH and Deploy
    shell: bash
    run: |
      apt-get update -y && apt-get install openssh-client -y
      mkdir dist
      echo "hi there" > dist/index.html
      mkdir -p ~/.ssh
      eval "$(ssh-agent -s)"
      echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
      echo "${{ secrets.DEPLOY_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_rsa
      chmod 600 ~/.ssh/id_rsa
      ssh-add ~/.ssh/id_rsa
      ssh-keyscan vm_ip_address >> ~/.ssh/known_hosts

      ssh -o StrictHostKeyChecking=no username@vm_ip_address "if [ -d /var/www/demo-app ]; then rm -rf /var/www/demo-app; fi"
      ssh -o StrictHostKeyChecking=no username@vm_ip_address "mkdir /var/www/demo-app"
      scp -r -o StrictHostKeyChecking=no published/* username@vm_ip_address:/var/www/demo-app
      ssh -o StrictHostKeyChecking=no username@vm_ip_address "sudo systemctl restart demo-app.service"
      echo 'deployment complete'
				
```
  • Go to your code, checkout the dev branch (since this will only run on the dev branch as specified in the beginning), and push to github, and voila.

Gitlab CI/CD

  • Copy the contents of ~/encoded_key.txt

  • Go to Your repo > Settings > CI/CD > Variables > Expand > Add Variable.

  • Give it a key, eg: DEPLOY_PRIVATE_KEY, and paste the content of ~/encoded_key.txt to the "Value" text area.

    note: you should also create secrets and variable for your VM IP address and ssh username, which we named "ci-user" earlier in the sudo permissions step, but I'll keep it simple for now.

  • In the root directory of your code, create the following file: .gitlab-ci.yml

  • Paste the following into the .gitlab-ci.yml file:

    stages:
        - build
        - deploy
    
    build_job:
        stage: build
        image: mcr.microsoft.com/dotnet/sdk:8.0
        script:
            - dotnet restore
            - dotnet build --no-restore
            - dotnet publish --no-restore --configuration Release -o published
        artifacts:
            paths:
                - published/
            expire_in: 1 hour
    
    deploy_job:
        stage: deploy
        image: ubuntu:latest
        before_script:
            - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
            - mkdir -p ~/.ssh
            - eval $(ssh-agent -s)
            - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
        script:
            - ssh-add <(echo "$DEPLOY_PRIVATE_KEY" | base64 -d)
            - ssh -o StrictHostKeyChecking=no ci-user@vm_ip_address "if [ -d /var/www/demo-app ]; then rm -rf /var/www/demo-app; fi"
            - ssh -o StrictHostKeyChecking=no ci-user@vm_ip_address "mkdir /var/www/demo-app"
            - scp -r -o StrictHostKeyChecking=no published/* ci-user@vm_ip_address:/var/www/demo-app
            - ssh -o StrictHostKeyChecking=no ci-user@vm_ip_address "sudo systemctl restart demo-app.service"
        only:
            - dev
    
    • Go to your code, checkout the dev branch (since this will only run on the dev branch as specified in the beginning), and push to gitlab, and voila.

Comments 0

Sign in to join the conversation.

No comments yet. Start the conversation.