Remote Deploy Toolkit — Automate deployment of Node.js / NestJS applications to remote servers via SSH.
npm install -g @deorta-dev/remotly
npx @deorta-dev/remotly deploy# 1. Create configuration
remotly init
ini
# 2. Edit .remotly-config/global.yml with your server details
# 3. Deploy
remotly deploy # interactive
remotly deploy --app api # headless
remotly deploy --parallel # multiple apps in parallel- Configuration Reference — All YAML parameters explained
- CLI Commands
- Programmatic API
- Plugin System
| Command | Description |
|---|---|
remotly deploy |
Deploy applications to remote servers |
remotly list |
List available applications |
remotly init |
Create global deploy configuration |
remotly init-app <name> |
Create per-app configuration |
remotly status |
Check status of deployed services |
remotly logs <app> |
Fetch logs from a deployed service |
remotly versions <app> |
List available deployment versions |
remotly rollback <app> |
Rollback to a previous version |
| Flag | Description | Default |
|---|---|---|
-a, --app <name> |
App to deploy (repeatable) | prompt |
-c, --config <dir> |
Config directory | ./.remotly-config |
-p, --parallel |
Deploy in parallel | false |
--no-build |
Skip build step | false |
--dry-run |
Preview without executing | false |
--env-file <path> |
Load additional .env file | — |
| Flag | Description | Default |
|---|---|---|
-n, --lines <number> |
Number of log lines | 50 |
-c, --config <dir> |
Config directory | ./.remotly-config |
| Flag | Description | Default |
|---|---|---|
-v, --version <version> |
Specific version to rollback to | previous version |
-c, --config <dir> |
Config directory | ./.remotly-config |
| Code | Meaning |
|---|---|
| 0 | All deploys successful |
| 1 | At least one deploy failed |
| 2 | Configuration error |
| 3 | SSH connection error |
# .remotly-config/global.yml
global:
transport:
type: ssh
host: "your-server.com"
port: 22
username: "deploy"
privateKeyPath: "~/.ssh/id_rsa"
nodeVersion: "20"
installPath: "/opt/services/{appName}"
# Service manager: "systemd" (default) or "pm2"
serviceManager: "systemd"
# Optional: pre-deploy commands
preDeploy:
- command: "mkdir -p /var/log/{appName}"
description: "Create log directory"
# Optional: system dependencies
systemDependencies:
check: true
apt:
- libnss3
# Optional: post-deploy commands
postDeploy:
- command: "systemctl reload nginx"
description: "Reload reverse proxy"
# Optional: versioning (timestamped releases + rollback)
versioning:
enabled: true
keepReleases: 5
# Optional: extra files/directories to upload
extraFiles:
- source: "scripts/init.sh"
- source: "config/"
destination: "/etc/myapp/config/"
env:
NODE_ENV: "production"
apps:
api:
env:
PORT: "3000"Resolution order: Defaults → global → apps[appName] → per-app file → REMOTLY_* env → CLI flags
See CONFIG_SCHEMA.md for the complete parameter reference.
import { deploy, deployMultiple, dryRun, scanApps, use } from '@deorta-dev/remotly';
import { firstValueFrom } from 'rxjs';
// Deploy a single app
const result = await firstValueFrom(deploy({ appName: 'api' }));
console.log(result.success); // true | false
// Deploy multiple
const results = await firstValueFrom(
deployMultiple([
{ appName: 'api' },
{ appName: 'worker' },
], { parallel: true }),
);
// Preview
const plan = await firstValueFrom(dryRun({ appName: 'api' }));
console.log(plan.steps);All async operations return RxJS Observables. The only exception is CLI command handlers which use firstValueFrom to bridge Observable → Promise.
| # | Step | Phase | Description |
|---|---|---|---|
| 1 | pre-deploy-commands |
build | Execute pre-deploy commands (local/remote) |
| 2 | build |
build | Execute build system |
| 3 | validate-output |
build | Verify output directory |
| 4 | connect |
transfer | SSH connection |
| 5 | ensure-node |
transfer | Verify/install Node.js on remote |
| 6 | system-dependencies |
transfer | Check/install system packages |
| 7 | prepare-dir |
transfer | Create remote directories (or release dir with versioning) |
| 8 | transfer |
transfer | Upload built files |
| 9 | upload-extra-files |
transfer | Upload extra files/directories |
| 10 | symlink-current |
transfer | Update current symlink (versioning only) |
| 11 | install |
install | Remote npm install |
| 12 | service-install |
install | Install service (systemd or PM2) |
| 13 | service-start |
start | Start/restart service |
| 14 | verify |
verify | Verify service is active |
| 15 | post-deploy-commands |
verify | Execute post-deploy commands (local/remote) |
Each phase has onBefore and onAfter hooks that plugins can intercept.
import { use } from '@deorta-dev/remotly';
import { nxPlugin } from '@deorta-dev/remotly-plugin-nx';
import { systemdPlugin } from '@deorta-dev/remotly-plugin-systemd';
import { sshPlugin } from '@deorta-dev/remotly-plugin-ssh';
use(nxPlugin());
use(systemdPlugin());
use(sshPlugin());| Package | Purpose |
|---|---|
@deorta-dev/remotly-plugin-nx |
NX scanner + builder |
@deorta-dev/remotly-plugin-systemd |
systemd service manager |
@deorta-dev/remotly-plugin-ssh |
SSH transport |
@deorta-dev/remotly-plugin-pm2 |
PM2 service manager |
@deorta-dev/remotly-plugin-docker |
Docker deployment |
import type { RemotlyPlugin } from '@deorta-dev/remotly';
import { of } from 'rxjs';
export const myPlugin = (): RemotlyPlugin => ({
name: 'my-plugin',
version: '1.0.0',
hooks: {
onAfterBuild: (ctx) => {
ctx.log('info', `Build completed for ${ctx.appName}`);
return of(void 0);
},
},
transports: [
{
name: 'my-transport',
transport: { /* ... */ },
},
],
});Remotly supports PM2 as an alternative to systemd for process management. Set serviceManager: "pm2" in your config:
global:
serviceManager: "pm2"PM2 is auto-installed on the remote server if not present. The pipeline generates an ecosystem.config.js file.
Enable timestamped releases to support rollback:
global:
versioning:
enabled: true
keepReleases: 5When versioning is enabled, each deploy creates a timestamped directory under releases/{YYYYMMDDHHmmss}/, and current becomes a symlink to the active release. Old releases beyond keepReleases are automatically cleaned up.
# List all versions
remotly versions api
# Rollback to previous version
remotly rollback api
# Rollback to specific version
remotly rollback api -v 20240711143000Upload additional files or directories with the deploy:
global:
extraFiles:
- source: "scripts/init.sh" # relative project path
- source: "config/"
destination: "/etc/myapp/config/" # remote destination (optional)If destination is omitted, the file is uploaded relative to the target installation directory.
Placeholders ${VAR_NAME} are resolved by chaining providers:
env:
DATABASE_URL: "${DB_PASSWORD}"Order: process.env → .env file → plugin providers (AWS Secrets Manager, Vault, etc.)
CLI layer (commander + inquirer + chalk)
↓
Core layer (Deployer + PipelineRunner)
↓
Scanner → Builder → Transport → ServiceManager → Secrets
Principles:
- Functional programming — no classes, factory functions
- RxJS Observables — no Promises in library code
- No shared state — each deploy is independent
- Plugin-based — everything is extensible
npm run build # tsup (ESM + CJS + DTS)
npm run dev # watch mode
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # vitest
npm run test:coverage # with coverage reportMIT — @deorta-dev