shiphound 1.0.1

dotnet tool install --global shiphound --version 1.0.1
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local shiphound --version 1.0.1
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=shiphound&version=1.0.1
                    
nuke :add-package shiphound --version 1.0.1
                    

ShipHound

NuGet License: MIT Build

Deploy full-stack apps to your VPS in one command.

ShipHound is a free, open-source CLI tool that automates the entire deployment pipeline: build locally, upload via SSH, configure Caddy reverse proxy with auto-HTTPS, set up systemd services, and go live.

Supports .NET and Node.js/Express backends with multi-service deployments. No Docker. No cloud vendor lock-in.

shiphound deploy

Features

  • One-Command Deploy - shiphound deploy handles everything
  • Multi-Service Support - Deploy multiple backend services (API + microservices) in one config
  • Multi-Backend Support - .NET and Node.js/Express backends (or mix both)
  • Zero-Downtime Deployments - Symlink-based switching for instant updates
  • Auto-HTTPS - Caddy handles SSL certificates automatically via Let's Encrypt
  • Automatic VPS Setup - shiphound setup installs Caddy, .NET, Node.js as needed
  • Auto-Restart - Systemd ensures your app restarts on crash or server reboot
  • Automatic Rollback - Failed deployments restore the previous version
  • Health Checks - Verifies your app responds after deployment
  • Pre-flight Checks - Validates SSH, disk space, and required software before deploying
  • Path-Based Routing - Multiple services sharing a domain with URL path routing
  • Per-Service Environment Variables - Global env vars with per-service overrides
  • Additional Paths - Deploy extra folders alongside your app (templates, seed data, etc.)
  • Deployment History - Local and remote deployment logging with git commit tracking

Prerequisites

  • .NET 8 SDK or later
  • A VPS running Ubuntu 22.04+ or Debian 11+ with SSH key access

Installation

dotnet tool install -g shiphound

Verify the installation:

shiphound --help

Update

dotnet tool update -g shiphound

Uninstall

dotnet tool uninstall -g shiphound

Quick Start

1. Initialize Your Project

cd /path/to/your/project
shiphound init

This creates a shiphound.yaml config file in your project root.

2. Edit shiphound.yaml

project: my-app

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

backend:
  path: ./backend
  port: 5000
  domain: api.myapp.com

env:
  DATABASE_URL: "postgres://localhost/mydb"

3. Setup Your VPS (First Time Only)

shiphound setup

This automatically installs Caddy, .NET runtime, Node.js, and configures directories and permissions.

4. Deploy

shiphound deploy

Your app is now live at https://myapp.com with automatic HTTPS.


Commands

Command Description
shiphound init Create a new shiphound.yaml config file
shiphound validate Validate your configuration
shiphound setup Set up VPS (installs Caddy, .NET/Node.js as needed)
shiphound setup --dry-run Preview what setup would install
shiphound deploy Deploy your app to VPS
shiphound deploy --dry-run Preview deployment without executing
shiphound deploy --skip-health-check Deploy without HTTP health checks
shiphound status Check app health and URLs
shiphound ps List all deployed apps on VPS
shiphound logs View application logs
shiphound logs -f Follow logs in real-time
shiphound logs --service api View logs for a specific service
shiphound logs --service caddy View Caddy access logs
shiphound history View deployment history
shiphound diagnose Run diagnostic checks on VPS
shiphound debug Debug deployment file structure and config
shiphound rollback Roll back to a previous deployment

Configuration Examples

Frontend Only (Static Site)

project: my-portfolio

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./
  domain: portfolio.com
  build: npm run build
  output: dist

env: {}

.NET Backend Only

project: my-api

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

backend:
  path: ./
  port: 5000
  domain: api.myapp.com

env:
  DATABASE_URL: "postgres://localhost/db"

Node.js/Express Backend Only

project: my-express-api

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

backend:
  type: node
  path: ./
  port: 3000
  domain: api.myapp.com
  entry: index.js

env:
  DATABASE_URL: "postgres://localhost/db"

Node.js with TypeScript

project: my-ts-api

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

backend:
  type: node
  path: ./
  port: 3000
  domain: api.myapp.com
  build: "npm run build"
  output: dist
  entry: index.js

env:
  DATABASE_URL: "postgres://localhost/db"

Full-Stack (React + .NET)

project: my-fullstack

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

backend:
  path: ./backend
  port: 5000
  domain: api.myapp.com

env:
  DATABASE_URL: "postgres://localhost/db"

Full-Stack (React + Node.js)

project: my-fullstack

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

backend:
  type: node
  path: ./backend
  port: 3000
  domain: api.myapp.com
  build: "npm run build"
  output: dist
  entry: index.js

env:
  DATABASE_URL: "postgres://localhost/db"

Multi-Service Deployment

Deploy multiple backend services with separate domains:

project: my-platform

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

services:
  - name: api
    path: ./backend
    port: 5000
    domain: api.myapp.com
    type: dotnet
    healthCheckPath: /health

  - name: notifications
    path: ./notification-service
    port: 5001
    domain: notifications.myapp.com
    type: node
    entry: index.js
    env:
      SMTP_HOST: "smtp.example.com"

env:
  DATABASE_URL: "postgres://localhost/db"

Multi-Service with Path-Based Routing

Multiple services sharing the same domain:

project: my-platform

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

services:
  - name: api
    path: ./backend
    port: 5000
    domain: api.myapp.com
    type: dotnet

  - name: notifications
    path: ./notification-service
    port: 5001
    domain: api.myapp.com
    domainPath: /notifications/*
    type: node
    entry: index.js

env:
  DATABASE_URL: "postgres://localhost/db"

Additional Paths

Deploy extra files/folders alongside your app:

project: my-app

vps:
  host: 123.45.67.89
  user: deploy
  sshKey: ~/.ssh/id_rsa

frontend:
  path: ./frontend
  domain: myapp.com
  build: npm run build
  output: dist

backend:
  type: node
  path: ./backend
  port: 3000
  domain: api.myapp.com
  entry: index.js

additionalPaths:
  - source: ./templates
    destination: templates
    exclude:
      - node_modules
      - .git
  - source: ./seed-data.json
    destination: data/seed.json

env:
  DATABASE_URL: "postgres://localhost/db"

Configuration Reference

# Project name (used for service names and directories)
project: my-app

# VPS connection settings
vps:
  host: 123.45.67.89       # VPS IP or hostname
  user: deploy             # SSH user with sudo access
  sshKey: ~/.ssh/id_rsa    # Path to SSH private key

# Runtime versions (installed by 'shiphound setup')
dotnetVersion: "8.0"       # .NET runtime version (e.g., "8.0", "9.0")
nodeVersion: "20"          # Node.js major version (e.g., "20", "22")

# Frontend configuration (optional)
frontend:
  path: ./frontend         # Path to frontend directory
  domain: myapp.com        # Domain for frontend
  build: npm run build     # Build command
  output: dist             # Build output directory
  healthCheckPath: /       # Health check path (default: /)

# Backend configuration (optional, use this OR services, not both)
backend:
  type: dotnet             # "dotnet" (default) or "node"
  path: ./backend          # Path to backend directory
  port: 5000               # Port your app listens on
  domain: api.myapp.com    # Domain for backend API
  healthCheckPath: /health # Health check endpoint

  # Node.js only options:
  build: "npm run build"   # Build command (optional)
  output: dist             # Output directory (optional)
  entry: index.js          # Entry point file (required for Node.js)

# Multi-service configuration (optional, use this OR backend, not both)
services:
  - name: api              # Unique service name (used in systemd unit name)
    type: dotnet           # "dotnet" (default) or "node"
    path: ./backend        # Path to service source
    port: 5000             # Port the service listens on
    domain: api.myapp.com  # Domain (optional, omit for internal-only services)
    domainPath: /api/*     # Path prefix for shared-domain routing (optional)
    healthCheckPath: /health
    build: "dotnet publish -c Release -o ./publish"  # Custom build command (optional)
    entry: index.js        # Node.js entry point (required for node type)
    output: dist           # Build output directory (optional)
    env:                   # Per-service env vars (merged with global, per-service wins)
      SERVICE_KEY: "value"

# Additional paths to deploy (optional)
additionalPaths:
  - source: ./templates        # Local path (file or directory)
    destination: templates     # Remote path relative to deploy directory
    exclude:                   # Directories to skip (optional)
      - node_modules
      - .git

# Environment variables (injected into all systemd services)
# Use single quotes for values containing special characters
# Example: ConnectionStrings__Default: 'Host=localhost;Password="secret"'
env:
  DATABASE_URL: "postgres://localhost/db"
  NODE_ENV: "production"

How It Works

1. PRE-FLIGHT CHECKS
   - Test SSH connection
   - Check disk space
   - Verify required runtimes (.NET/Node.js)
   - Validate DNS records

2. BUILD (Local)
   - npm run build (frontend)
   - dotnet publish / npm build (each service)

3. BACKUP
   - Create timestamped backup of current deployment

4. UPLOAD
   - Upload files via SFTP to new deploy directory
   - Upload additional paths (with exclude support)
   - For Node.js: runs npm install on VPS

5. CONFIGURE
   - Generate Caddy config (reverse proxy + HTTPS)
   - Generate systemd service per backend service

6. ACTIVATE
   - Update current symlink to new deploy
   - Restart services
   - Verify services stay running (3s stability check)
   - Reload Caddy

7. HEALTH CHECK
   - Verify each service responds via localhost
   - Rollback automatically if any check fails

VPS Directory Structure

After deployment, your VPS has this structure:

/var/www/{project}/
  ├── current/           -> symlink to deploys/{timestamp}
  ├── deploys/
  │   └── 20240115-143052/
  │       ├── frontend/
  │       ├── api/
  │       └── notifications/
  ├── backups/
  │   └── 20240115-140000/
  └── .shiphound/

Health Checks

After deployment, ShipHound verifies your app is running by sending HTTP requests to each service via SSH (curl on localhost). A response with status code 200-399 is considered healthy. If health checks fail, ShipHound automatically rolls back to the previous deployment.

By default, ShipHound checks the root path (/). Most APIs don't serve anything at /, which causes a false failure. You should either:

  1. Set a healthCheckPath that returns a 200 response:
backend:
  path: ./backend
  port: 5000
  domain: api.myapp.com
  healthCheckPath: /health
  1. Skip health checks if your app has no health endpoint:
shiphound deploy --skip-health-check

The service stability check (verifies the process stays running via systemd) always runs regardless of the --skip-health-check flag.

Health check behavior:

  • Waits 2 seconds after service start before first check
  • Retries up to 3 times with exponential backoff
  • Shows which URL is being checked and the attempt number
  • On failure, shows the HTTP status code returned
  • Automatically rolls back on failure

VPS Requirements

  • Ubuntu 22.04+ or Debian 11+
  • SSH access with key authentication
  • Passwordless sudo configured

To enable passwordless sudo:

ssh your-user@your-vps
echo "$USER ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/$USER

Everything else (Caddy, .NET, Node.js) is installed automatically by shiphound setup.


Troubleshooting

SSH Connection Failed

# Test SSH manually
ssh -i ~/.ssh/id_rsa deploy@your-vps-ip

# Check SSH key permissions (Linux/Mac)
chmod 600 ~/.ssh/id_rsa

Backend Won't Start

# Check service logs
shiphound logs --service api

# Or SSH and check directly
ssh deploy@your-vps
sudo journalctl -u my-app-api -f

Caddy/HTTPS Issues

# Check Caddy logs
shiphound logs --service caddy

# Verify DNS points to your VPS
nslookup yourdomain.com

Deployment Failed

ShipHound automatically rolls back on failure. Check the error message and logs:

shiphound diagnose
shiphound logs
shiphound debug

Service Crash-Looping

If a service starts but crashes immediately, ShipHound detects this during the stability check and shows the last 15 lines of logs. Common causes:

  • Missing environment variables
  • Wrong port configuration
  • Database connection issues
  • Missing dependencies

Wrong .NET / Node.js Version

If your app targets a different runtime version than what's installed on the VPS:

dotnetVersion: "9.0"
nodeVersion: "22"

Then run shiphound setup again to install the correct version. Preflight checks will block deploys if the required version isn't found.

Health Check Fails But App Is Running

If the stability check passes but health check fails, your app is running but not responding on the checked path. By default ShipHound checks / which many APIs don't serve.

Fix: Set healthCheckPath to an endpoint that returns HTTP 200, or use --skip-health-check.

Environment Variables Not Working

YAML has special characters that can break parsing. If your env values contain quotes, colons, or special characters, use single quotes:

env:
  # BAD - nested double quotes break YAML:
  ConnectionStrings__Default: "Host=localhost;Password="secret""

  # GOOD - single quotes handle inner quotes:
  ConnectionStrings__Default: 'Host=localhost;Password="secret"'

Check the generated service file on your VPS:

cat /etc/systemd/system/{project}-{service}.service

FAQ

Q: What backend frameworks are supported? A: .NET and Node.js/Express. Set type: node for Node.js backends.

Q: Can I deploy multiple services? A: Yes. Use the services: config instead of backend: to define multiple named services, each with their own port, domain, and runtime type.

Q: Do I need Docker? A: No. ShipHound deploys apps natively on Linux using systemd and Caddy.

Q: How does Node.js deployment work? A: ShipHound uploads your code (excluding node_modules), runs npm install --production on the VPS, and creates a systemd service.

Q: Will my app restart if it crashes? A: Yes. The systemd service is configured with Restart=always and starts on boot.

Q: What happens if deployment fails? A: ShipHound automatically restores your previous version from the backup directory.

Q: Can services share a domain? A: Yes. Use domainPath on services to route different URL paths to different services on the same domain.

Q: Can I deploy extra files like templates or seed data? A: Yes. Use additionalPaths to deploy any extra files or directories alongside your app, with optional exclude patterns.

Q: What VPS providers work with ShipHound? A: Any provider that gives you a Linux VPS with SSH access: DigitalOcean, Hetzner, Linode, Vultr, AWS Lightsail, etc.

Q: Can I use ShipHound with a monorepo? A: Yes. Point frontend.path and each service path to the correct subdirectory within your repo.


Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

git clone https://github.com/CydoEntis/ShipHound.git
cd ShipHound
dotnet build
dotnet test

License

MIT


Deploy with confidence. Ship with ShipHound.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.0.1 67 3/3/2026
1.0.0 81 2/21/2026