Deploy releases over SSH with rsync, archive ZIP / TAR, symlinks, SCP ...
Example :
/deployPath
|
├── www --> symlink to ./releases/<currentRelease>
|
├── releases
| ├── 2017-02-08-17-14-21-867-UTC
| ├── ...
| └── 2017-02-09-18-01-10-765-UTC
| ├── ...
| └── logs --> symlink to shared/logs
|
├── synchronized --> folder synchronized with rsync
|
└── shared
└── logs
npm install ssh-deploy-release
const Application = require('ssh-deploy-release');
const options = {
localPath: 'src',
host: 'my.server.com',
username: 'username',
password: 'password',
deployPath: '/var/www/vhost/path/to/project'
};
const deployer = new Application(options);
deployer.deployRelease(() => {
console.log('Ok !')
});
const Application = require('ssh-deploy-release');
const options = {
localPath: 'src',
host: 'my.server.com',
username: 'username',
password: 'password',
deployPath: '/var/www/vhost/path/to/project',
allowRemove: true
};
const deployer = new Application(options);
deployer.removeRelease(() => {
console.log('Ok !')
});
const Application = require('ssh-deploy-release');
const options = {
localPath: 'src',
host: 'my.server.com',
username: 'username',
password: 'password',
deployPath: '/var/www/vhost/path/to/project'
};
const deployer = new Application(options);
deployer.rollbackToPreviousRelease(() => {
console.log('Ok !')
});
The previous release will be renamed before updating the symlink of the current version, for example
2019-01-09-10-53-35-265-UTC
will become
2019-01-09-13-46-45-457-UTC_rollback-to_2019-01-09-10-53-35-265-UTC
.
If rollbackToPreviousRelease
is called several times, the current version
will switch between the last two releases.
current date + "_rollbackTo_"
will be prepended to the release name on each call of
rollbackToPreviousRelease
so be careful not to exceed the size limit of the folder name.
You can use this to deploy from any platform supporting Node >= 8 to Linux servers (most UNIX systems should work as well but this hasn't been tested).
Due to how we implemented the deployment process on the remote environment (using shell command execution), supporting Windows would required a lot of specific code, which would make this package harder to maintain. We decided to focus on supporting Linux as its the platform most widely used by hosting providers.
ssh-deploy-release uses ssh2 to handle SSH connections.
The options
object is forwarded to ssh2
methods,
which means you can set all ssh2
options:
If true
, will display all commands.
Default : false
Port used to connect to the remote server.
Default : 22
Remote server hostname.
Username used to connect to the remote server.
Password used to connect to the remote server.
Default: null
Default: null
For an encrypted private key, this is the passphrase used to decrypt it.
Default: null
To connect using the machine's ssh-agent. The value must be the path to the ssh-agent socket (usually available in the
SSH_AUTH_SOCK
environment variable).
archive
: Deploy an archive and decompress it on the remote server.
synchronize
: Use rsync. Files are synchronized in the options.synchronized
folder on the remote server.
Default : archive
zip
: Use zip compression (unzip
command on remote)
tar
: Use tar gz compression (tar
command on remote)
Default : tar
Name of the archive.
Default : release.tar.gz
Delete the local archive after the deployment.
Default : true
SCP connection timeout duration.
Default : 20000
Callback passed to ssh2
client event
keyboard-interactive
.
Type: function (name, descr, lang, prompts, finish)
Name of the current release symbolic link. Relative to deployPath
.
Defaut : www
Name of the folder containing shared folders. Relative to deployPath
.
Default : shared
Name of the folder containing releases. Relative to deployPath
.
Default : releases
Name of the local folder to deploy.
Default : www
⚠ ️In case you need to deploy your whole project directory, do NOT set localPath
to an empty string, null
or .
. Use process.cwd()
to have node generate an
absolute path. In addition to this, if you use the archive mode,
don't forget to exclude the generated archive
(you can define its name using options.archiveName).
Example:
const Application = require('ssh-deploy-release');
const process = require('process');
const deployer = new Application({
localPath: process.cwd(),
exclude: ['release.tar.gz'],
archiveName: 'release.tar.gz',
host: 'my.server.com',
username: 'username',
password: 'password',
deployPath: '/var/www/vhost/path/to/project',
});
Absolute path on the remote server where releases will be deployed. Do not specify currentReleaseLink (or www folder) in this path.
Name of the remote folder where rsync synchronize release.
Used when mode
is 'synchronize'.
Default : www
Additional options for rsync process.
Default : ''
rsyncOptions : '--exclude-from="exclude.txt" --delete-excluded'
Enable the rsync --compression flag. This can be set to a boolean or an integer to explicitly set the compression level (--compress-level=NUM).
Default : true
Number of releases to keep on the remote server.
Default : 3
Name of the release. Must be different for each release.
Default : Use current timestamp.
List of paths to not deploy.
Paths must be relative to localPath
.
The format slightly differ depending on the mode
:
mode: 'archive'
**
.exclude: ['my-folder/**']
Read glob documentation for more information.
mode: 'synchronize'
exclude: ['my-folder']
For maximum portability, it's strongly advised to use both syntaxes when excluding folders.
For example: exclude: ['my-folder/**', 'my-folder']
Default : []
List of folders to "share" between releases. A symlink will be created for each item.
Item can be either a string or an object (to specify the mode to set to the symlink target).
share: {
'images': 'assets/images',
'upload': {
symlink: 'app/upload',
mode: '777' // Will chmod 777 shared/upload
}
}
Keys = Folder to share (relative to sharedFolder
)
Values = Symlink path (relative to release folder)
Default : {}
List of folders to create on the remote server.
Default : []
List of files to make writable on the remote server. (chmod ugo+w)
Default : []
List of files to make executable on the remote server. (chmod ugo+x)
Default : []
If true, the remote release folder can be deleted with removeRelease
method.
Default: false
The following object is passed to onXXX
callbacks :
{
// Loaded configuration
options: { },
// Release
release: {
// Current release name
tag: '2017-01-25-08-40-15-138-UTC',
// Current release path on the remote server
path: '/opt/.../releases/2017-01-25-08-40-15-138-UTC',
},
// Logger methods
logger: {
// Log fatal error and stop process
fatal: (message) => {},
// Log 'subhead' message
subhead: (message) => {},
// Log 'ok' message
ok: (message) => {},
// Log 'error' message and continue process
error: (message) => {},
// Log message, only if options.debug is true
debug: (message) => {},
// Log message
log: (message) => {},
// Start a spinner and display message
// return a stop()
startSpinner: (message) => { return {stop: () => {}}},
},
// Remote server methods
remote: {
// Excute command on the remote server
exec: (command, done, showLog) => {},
// Excute multiple commands (array) on the remote server
execMultiple: (commands, done, showLog) => {},
/*
* Upload local src file to target on the remote server.
* @param {string} src The path to the file to upload.
* May be either absolute or relative to the current working directory.
* @param {string} target The path of the uploaded file on the remote server.
* Must include the filename. The full directory hierarchy to the target must already exist.
* May be either absolute or relative to the remote user home directory.
* We strongly encourage you to use `options.deployPath` in your target path to produce an absolute path.
*/
upload: (src, target, done) => {},
// Create a symbolic link on the remote server
createSymboliclink: (target, link, done) => {},
// Chmod path on the remote server
chmod: (path, mode, done) => {},
// Create folder on the remote server
createFolder: (path, done) => {},
}
}
onBeforeDeploy, onBeforeLink, onAfterDeploy, onBeforeRollback, onAfterRollback options.
onAfterDeploy: 'apachectl graceful'
Or with a function :
onBeforeLink: context => `chgrp -R www ${context.release.path}`
onAfterDeploy: [
'do something on the remote server',
'and another thing'
]
Or with a function :
onBeforeLink: (context) => {
context.logger.subhead('Fine tuning permissions on newly deployed release');
return [
`chgrp -R www ${context.release.path}`,
`chmod g+w ${context.release.path}/some/path/that/needs/to/be/writable/by/www/group`,
];
}
onAfterDeploy: context => {
return Promise((resolve, reject) => {
setTimeout(function () {
// Do something
resolve();
}, 5000);
});
}
Executed before connecting to the SSH server to let you initiate a custom
connection. It must return a ssh2 Client instance, and call onReady
when that
connection is ready.
Type: function(context, onReady, onError, onClose): Client
Example: SSH jumps (connecting to your deployment server through a bastion)
onBeforeConnect: (context, onReady, onError, onClose) => {
const bastion = new Client();
const connection = new Client();
bastion.on('error', onError);
bastion.on('close', onClose);
bastion.on('ready', () => {
bastion.forwardOut(
'127.0.0.1',
12345,
'www.example.com',
22,
(err, stream) => {
if (err) {
context.logger.fatal(`Error connection to the bastion: ${err}`);
bastion.end();
onClose();
return;
}
connection.connect({
sock: stream,
user: 'www-user',
password: 'www-password',
});
}
);
});
connection.on('error', (err) => {
context.logger.error(err);
bastion.end();
});
connection.on('close', () => {
bastion.end();
});
connection.on('ready', onReady);
bastion.connect({
host: 'bastion.example.com',
user: 'bastion-user',
password: 'bastion-password',
});
return connection;
}
Executed before deployment.
Type: string | string[] | function(context, done): Promise | undefined
Executed before symlink creation.
Type: string | string[] | function(context, done): Promise | undefined
Executed after deployment.
Type: string | string[] | function(context, done): Promise | undefined
Executed before rollback to previous release.
Type: string | string[] | function(context, done): Promise | undefined
Executed after rollback to previous release.
Type: string | string[] | function(context, done): Promise | undefined
A command on a callback method is not executed or not found.
Try to add set -i && source ~/.bashrc &&
before your commmand :
onAfterDeploy:[
'set -i && source ~/.bashrc && my command'
]
See this issue : https://github.com/mscdex/ssh2/issues/77
# Build (with Babel)
npm run build
# Build + watch (with Babel)
npm run build -- --watch
# Launch tests (Mocha + SinonJS)
npm test
# Launch tests + watch (Mocha + SinonJS)
npm test -- --watch