poseup v0.4.3
Poseup
If you find it useful, consider starring the project 💪 and/or following its author ❤️ -there's more on the way!
Install
Requires docker to be installed on your system.
To install poseup globally, run: npm install -g poseup
Motivation
Poseup leverages docker-compose with the goal of making having a containerized development and test workflow trivial. It is governed by a poseup.config file that can be used as a single source of truth, as poseup is also able to generate traditional docker-compose files from it.
Ultimately, it is an effort to fix the most common issues with similar solutions, which tend to be:
- Unable to properly clean up ephemeral services when errors occur or the process is terminated by the user.
- Lacking in compatibility with
docker-compose, making it difficult to have a single source of truth. - Lacking in configuration and CLI options, as well as clarity as to what goes on under the hood.
In contrast with other solutions, poseup integrates seamlessly with docker-compose and its configuration format. Mostly as a product of it, poseup is able to:
- Properly clean up ephemeral services in all events but a forced kill to the process.
- Run tasks either with a set of persisted services or in completely ephemeral sandboxes -see
poseup run. - Run automated setup commands for services before tasks are run -see
exec. - Seek setups for different environments in a single source of truth -see
environments. - Produce
docker-composeconfiguration files from its ownposeup.config-seeposeup compose. - Inherit all
docker-composecommands, hence allowing for a high degree of flexibility -seeposeup compose.
Usage
- CLI
poseupoptions runsdocker-composeas per yourposeup.configfile.poseup runis a task runner for docker.poseup cleancleans all non persisted containers.poseup purgepurges volumes, networks, and images from your system.- Common options: a description of common options taken by CLI commands.
- Configuration: how to get the most out of poseup through your
poseup.configfile.- Extensions: the allowed file extensions for a config file.
- Path: how poseup resolves the path for your config file by default.
- Structure: what you should put where in the configuration file.
- Environments: how to define different configurations for several environments.
- Example: a complete example configuration.
- Programmatic usage: how to use poseup programmatically.
CLI
poseup options
Usage:
$ poseup [option]
$ poseup [command] [options]
Options:
-d, --dir <dir> Project directory
-f, --file <path> Path for config file [js,json,yml,yaml]
-e, --env <env> Node environment
--log <level> Logging level
-h, --help Show help
-v, --version Show version number
Commands:
compose Runs docker-compose
run Runs tasks
clean Cleans not persisted containers and networks -optionally, also volumes
purge Purges dangling containers, networks, and volumes system-wide
Examples:
$ poseup --log debug compose -- up
$ poseup -d ./foo -e development cleanlog
Sets logging level. Can be one of silent, trace, debug, info, warn, error.
Example: poseup --log debug clean
file
Sets the poseup configuration file as an absolute or relative path to cwd. By default, poseup will attempt to find it in the project directory -if passed- or cwd, and up.
Example: poseup --file ../poseup.development.js compose -- up
dir
Sets the project directory for docker as an absolute or relative path to cwd. By default, it is the directory where the poseup configuration file is found.
Example: poseup --dir ../ compose -- dowm
env
Assigns an arbitrary value to process.env.NODE_ENV.
Example: poseup --env development compose -- up
The above would be equivalent to NODE_ENV=development poseup compose -- up
poseup compose
Runs docker-compose as per the services defined in your poseup.config or, alternatively, produces a docker compose file from the compose object of your poseup configuration.
Example: poseup compose -- up
Usage:
$ poseup compose [options] -- [dockerArgs]
Runs docker-compose
Options:
-w, --write <path> Produce a docker compose file and save it to path
-s, --stop Stop all services on exit
-c, --clean Run clean on exit
--dry Dry run -write docker compose file only
-h, --help Show helpGenerating a docker-compose file
To generate a docker-compose compatible file, you can just run: poseup compose --dry --write docker-compose.yml.
If your poseup.config configuration depends on NODE_ENV, you could just run poseup compose -e development --dry --write docker-compose.development.yml to get the docker-compose configuration for the development environment.
poseup run
A task runner. Runs a task or a series of tasks -defined in your poseup.config- in a one off primary container while starting its dependent services, or in a single use sandbox -which creates new containers for the task runner and all its services, and removes them after the task has finished execution.
Usage:
$ poseup run [options] [tasks]
Runs tasks
Options:
-l, --list List tasks
-s, --sandbox Create new containers for all services, remove all on exit
-t, --timeout <seconds> Timeout for waiting time after starting services before running commands [${RUN_WAIT_TIMEOUT} by default]
--no-detect Prevent service initialization auto detection and wait until timeout instead
-h, --help Show helpposeup clean
Cleans all services absent from the persist array of your poseup.config file.
Usage:
$ poseup clean [options]
Cleans not persisted containers and networks -optionally, also volumes
Options:
-v, --volumes Clean volumes not associated with persisted containers
-h, --help Show helpposeup purge
Shorthand for a serial run of docker volume prune, docker network prune, docker image prune --all, and docker container ls --all.
Usage:
$ poseup purge [options]
Purges dangling containers, networks, and volumes system-wide
Options:
-f, --force Skip confirmation
-h, --help Show helpConfiguration
A poseup configuration file is the single most important thing for you to make your poseup usage worthwhile. It contains the project name, the persisted containers, any number of tasks, and a docker-compose configuration object containing services, volumes, and networks definition. Before you jump in, you can check out an example here.
Extensions
Valid configuration files are .js, .json, or .yml files, though you'll have to use a .js file in order to be able to define different configurations depending on environment on the same file. See environments.
Path
All poseup commands but poseup purge (since it's system-wide) will need a poseup.config file. You can pass the file path via the --file flag, but otherwise, poseup will look for a poseup.config.{js,json,yml} in the current working directory and up. Remember you can also specify a different working directory for poseup than the current on console with the --dir flag.
Structure
Root properties are:
log: string, optional, logging level. One of:'silent','trace','debug','info','warn','error'.project: string, required, the name of the project. Note that you should never have different environment-dependent configurations for a same project name (see environments).persist: strings array, optional, the name of the services to not clean when runningposeup cleanandposeup run. This is useful if you have services you don't want to be ephemeral, like a database, which data you'd like to keep between runs.compose: object, required, should have the same structure as a traditionaldocker-composefile. Here is where you define your services and their configuration. Having yourdocker-composeconfiguration inside the poseup config will allow you, if desired, to have all environmental differences in a single file, while you can always generate an actualdocker-composeyaml file for any environment from it by dry runningposeup compose.tasks: object, optional, any number of tasks to be executed viaposeup run. The keys of thetaskobject will be the names of your task, and each have an object as value with optional keys:description: string, a description for the task -it'll be used when runningposeup run --list.primary: string, the name of the service for which a new ephemeral container will be created in order to runcmd. If noprimaryis specified,cmdwill be run in your local environment -your system.services: strings array, the names of the associated services required to run this task. If not present and aprimaryservice has been specified, poseup will use the services defined on thedepends_onkey of the service in thecomposedefinition by default. If it exists, it will not merge withdepends_on, this way you can restrict or extend the required services for this task with no regard for thecomposedefinition. If not present and noprimaryhas been defined, no services will be started for the task, though that would mean you'd essentially be just running a local command, which would make little sense.cmd: strings array, the command to execute onprimaryor locally that represents the task itself. Example:['npm', 'install'].exec: array | object any number of commands to execute on any of the services that are required to start when running the task -either viaservicesor thecomposedepends_on- beforecmdis run. Each item of the array must be an object. The keys of the object will signal the service to run the command on, meanwhile their value should be a strings array for the command. All the commands in each item of theexecarray will be executed with no guaranteed execution order, while each array item will be guaranteed to run serially:
// Unordered execution example
({
tasks: {
myTask: {
primary: 'myPrimaryService',
cmd: ['echo', 'foo'],
exec: [
{
// These will execute in parallel
myDbService: ['psql', '-U', 'postgres', '-c', 'CREATE DATABASE testdb;'],
myNodeService: ['npm', 'install']
}
]
}
}
});
// Unordered execution example (equivalent to the previous)
({
tasks: {
myTask: {
primary: 'myPrimaryService',
cmd: ['echo', 'foo'],
exec: {
// These will execute in parallel
myDbService: ['psql', '-U', 'postgres', '-c', 'CREATE DATABASE testdb;'],
myNodeService: ['npm', 'install']
}
}
}
});
// Series example: guaranteed execution order
({
tasks: {
myTask: {
primary: 'myPrimaryService',
cmd: ['echo', 'foo'],
exec: [
// These will execute in series
{ myDbService: ['psql', '-U', 'postgres', '-c', 'CREATE DATABASE testdb;'] },
{ myNodeService: ['npm', 'install'] }
]
}
}
});Environments
There are two strategies you can follow in order to define different configurations for several environments.
It's important to keep in mind, regardless of the strategy you use, poseup will treat any configuration with the same project name as if they were the same. What that means is you need to make sure that, for different configuration values, the project name in your poseup.config file is distinct, otherwise things could get quite messy. That being said:
- you can have several
poseup.configfiles and pass them to poseup depending on the one you desire to apply via the--fileflag, - or you can use a JavaScript file for your
poseup.configthat defines a different configuration as per any arbitrary number of environment variables.
Focusing on the single JavaScript file strategy, a simple solution would be to do something like:
poseup.config.js:
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
project: 'my-project-name' + (isProduction ? 'production' : 'development'),
persist: [ /* ...my persisted services */ ],
tasks: isProduction
? { /* ...my production tasks */ }
: { /* ...my development tasks */ },
compose: {
version: '3.4',
services: { /* ...my services */ }
}
};Solutions like slimconf might assist you in organizing your environment-dependent configuration.
As an example:
const { default: slim, fallback } = require('slimconf');
module.exports = slim(
{ env: [process.env.NODE_ENV, fallback('development', ['test']) },
(on, { env }) => ({
project: `my-project-${env}`,
persist: on.env({
default: [
// ...my default persisted services
],
development: [
// ...my persisted services in development
],
test: [] // all services will be ephemeral on test
}),
tasks: on.env({
default: {
// ...my default tasks
},
development: {
// ...my development tasks
},
test: {
// ...my test tasks
}
}),
compose: {
version: '3.4',
services: {
// ...my services
}
}
})
);Example
This example uses slimconf to manage environment-dependent configuration.
poseup.config.js:
const { default: slim, fallback, merge } = require('slimconf');
module.exports = slim(
{ env: [process.env.NODE_ENV, fallback('development', ['test']) },
// We're merging the defaults with the environment-dependent configuration
(on, { env }) => on.env(merge, {
/* Defaults */
defaults: {
log: 'info',
project: `my-project-${env}`,
compose: {
version: '3.4',
services: {
app: {
image: 'node:8-alpine',
depends_on: ['db'],
networks: ['backend'],
environment: {
NODE_ENV: env,
DB_URL: `postgres://postgres:pass@db:5432/testdb`
},
volumes: [{ type: 'bind', source: './', target: '/usr/src/app' }]
},
db: {
image: 'postgres:11-alpine',
networks: ['backend'],
environment: { POSTGRES_PASSWORD: 'pass' }
}
},
networks: { backend: {} },
volumes: {}
}
},
/* Development environment */
development: {
// We'll persist the database on development (won't be removed on poseup clean)
persist: ['db'],
tasks: {
// This will run "node index.js" locally after "db" is up.
// We'd run it with: poseup run local
local: {
services: ['db'],
cmd: ['/bin/sh', '-c'].concat('cd /usr/src/app && node index.js')
},
// This will run "node index.js" in a docker container ("app").
// Since app already depends on "db", we don't need to define it.
// We'd run it with: poseup run docker
docker: {
primary: 'app',
cmd: ['/bin/sh', '-c'].concat('cd /usr/src/app && node index.js')
},
// This will just setup the database for usage on development.
// As we are persisting the container, it's a one-off task.
bootstrap: {
services: ['db'],
exec: {
db: 'psql -U postgres -c'
.split(' ')
.concat('CREATE DATABASE testdb;')
}
}
},
compose: {
// We'll also expose ports
services: {
app: { ports: ['3000:3000'] },
db: { ports: ['5432:5432'] }
}
}
},
/* Test environment */
test: {
tasks: {
// We'd run it with: poseup run -e test jest
jest: {
primary: 'app',
cmd: ['/bin/sh', '-c'].concat('cd /usr/src/app && npx jest'),
// Create database before running tests
exec: {
db: 'psql -U postgres -c'
.split(' ')
.concat('CREATE DATABASE testdb;')
}
}
}
}
})
);Programmatic Usage
poseup exports compose, run, clean, and purge, which are called by the equally named CLI commands.
However, when running poseup on the CLI, the program will also listen to termination events through exits and run cleanup tasks either at end of execution or termination signals. In order to handle these cleanup tasks, you have two options: