@deorta-dev/remotly
v2.0.0
Published
Remote Deploy Toolkit — deploy Node apps via SSH
Downloads
378
Readme
@deorta-dev/remotly
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 deployQuick Start
# 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 parallelDocumentation
- Configuration Reference — All YAML parameters explained
- CLI Commands
- Programmatic API
- Plugin System
CLI Commands
| 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 |
deploy Options
| 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 | — |
logs Options
| Flag | Description | Default |
|------|-------------|---------|
| -n, --lines <number> | Number of log lines | 50 |
| -c, --config <dir> | Config directory | ./.remotly-config |
rollback Options
| Flag | Description | Default |
|------|-------------|---------|
| -v, --version <version> | Specific version to rollback to | previous version |
| -c, --config <dir> | Config directory | ./.remotly-config |
Exit Codes
| Code | Meaning | |------|---------| | 0 | All deploys successful | | 1 | At least one deploy failed | | 2 | Configuration error | | 3 | SSH connection error |
Configuration
# .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.
Programmatic API
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.
Pipeline
| # | 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.
Plugins
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());Official Plugins
| 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 |
Creating a Plugin
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: { /* ... */ },
},
],
});PM2 Support
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.
Versioning & Rollback
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 20240711143000Extra Files
Upload 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.
Secrets
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.)
Architecture
CLI layer (commander + inquirer + chalk)
↓
Core layer (Deployer + PipelineRunner)
↓
Scanner → Builder → Transport → ServiceManager → SecretsPrinciples:
- 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
Development
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 reportLicense
MIT — @deorta-dev
