.NET React Templates

Deployments

Deployments with GitHub Actions, Kamal, Docker and SSH

Docker + Kamal

All project templates includes the necessary GitHub Actions for CI/CD with automatic Docker image builds and production deployment with Kamal to any Linux server with SSH.

If you don't have a Linux Server, we recommend Hetzner who we've found offers the best value US Cloud VMs offering $15/mo for a dedicated VM with 2vCPU / 8GB RAM which is being used to host 30 Docker Apps, including all project template live demos - using the GitHub Actions included in each template.

  1. Create the SSH Private and Public Keys
ssh-keygen -t ed25519 -C "deploy@myapp" -f ./deploy-key
  1. Copy Public Key to deployment server to enable SSH access
cat ~/deploy-key.pub | ssh <user>@<your-ip> "cat >> ~/.ssh/authorized_keys"
  1. Configure GitHub Secrets for Kamal to access your deployment server
# SSH private key to access the server: ssh-rsa ...
gh secret set SSH_PRIVATE_KEY < ~/deploy-key

# IP address of the server to deploy to: 100.100.100.100
gh secret set KAMAL_DEPLOY_IP <your.ip>

# Email for Let's Encrypt SSL certificate: me@example.org
gh secret set LETSENCRYPT_EMAIL <your@email>

These Required variables can be globally configured in your GitHub Organization or User secrets which will enable deploying all your Repositories to the same server.

Optionally configure any other global secrets to be shared by all Apps here:

gh secret set SERVICESTACK_LICENSE <license-key>
  1. Configure GitHub Secrets for your App

The only secret that needs to be configured per App is:

# Hostname used for SSL certificate and Kamal proxy: www.example.org
gh secret set KAMAL_DEPLOY_HOST <www.example.org>

App Settings Secrets

You could register any App-specific secrets here, although our preference is to instead save all your secrets in a single APPSETTINGS_JSON GitHub Action Secret which will get written inside the Docker container appsettings.Production.json, e.g:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=service-postgres;Port=5432;User Id=dbuser;Password=dbpass;Database=dbname;Pooling=true;"
  },
  "SmtpConfig": {
      "UserName": "SmtpUser",
      "Password": "SmtpPass",
      "Host": "email-smtp.us-east-1.amazonaws.com",
      "Port": 587,
      "From": "noreply@example.org",
      "FromName": "MyApp",
      "Bcc": "copy@example.org"
    } 
  },
  "Admins": ["admin1@email.com","admin2@email.com"]
}

After changing appsettings.Production.json update your APPSETTINGS_JSON GitHub Action Secret with:

npm run secret:prod

This uses the GitHub CLI to add your appsettings.Production.json to your GitHub repository's Action secrets:

gh secret set APPSETTINGS_JSON < appsettings.Production.json

How It Works:

  1. Development - Create appsettings.Production.json locally with your production configuration
  2. Upload - Run npm run secret:prod to store it as a GitHub Action secret (never committed to git)
  3. Deployment - GitHub Actions injects the secret as the APPSETTINGS_JSON_BASE64 environment variable
  4. Runtime - The container startup script decodes and writes it to /app/dotnet/appsettings.Production.json
  5. Isolation - The file is written with root-only permissions, preventing Node.js access

Configuration in config/deploy.yml:

# config/deploy.yml
env:
  secret:
    - APPSETTINGS_JSON_BASE64  # Base64-encoded production config

Benefits:

  • Secrets never committed to git repository
  • Secrets never baked into Docker image layers
  • Same Docker image can be used across all environments
  • Production configuration remains isolated from Node.js process

Inferred Variables

These variables are inferred from the GitHub Action context and don't need to be configured.

VariableSourceDescription
GITHUB_REPOSITORY${{github.repository}}acme/example.org - used for service name and image
KAMAL_REGISTRY_USERNAME${{github.actor}}GitHub username for container registry
KAMAL_REGISTRY_PASSWORD${{secrets.GITHUB_TOKEN}}GitHub token for container registry auth

Kamal deploy.yml

The /config/deploy.yml configuration is designed to be reusable across projects as it dynamically derives service names, image paths, and volume mounts from environment variables, so you only need to configure your server's IP and hostname using GitHub Action secrets.

To also be able to use kamal locally to inspect logs or deploy from your own server you can add the GitHub Action Secrets into a local .env file, e.g:

# Required for Kamal deploy.yml template processing

GITHUB_REPOSITORY=acme/example.org

# Server deployment details
KAMAL_DEPLOY_IP=100.100.100.100
KAMAL_DEPLOY_HOST=example.org

# Container registry credentials (for ghcr.io)
KAMAL_REGISTRY_USERNAME=my-user
GITHUB_TOKEN=ghp_xxx

# Login to GitHub Container Registry
#echo $KAMAL_REGISTRY_PASSWORD | docker login ghcr.io -u my-user --password-stdin

You can then load the environment variables into your shell with:

. ./load-env.sh

Which will allow you to use kamal locally to access your deployment server, e.g:

kamal app logs -f

Hard code App specific variables

Alternatively if you don't want to maintain a .env with GitHub Action Secrets you can hard-code all App-specific variables in your deploy.yml file so it doesn't need to perform any template processing for its Environment Variable substitutions.

Features

  • Docker containerization with optimized .NET images
  • SSL auto-certification via Let's Encrypt
  • GitHub Container Registry integration
  • Volume persistence for App_Data including any SQLite database