If you find it useful, consider starring the project 💪 and/or following its author ❤️ -there's more on the way!
Requires docker
to be installed on your system.
To install poseup globally, run: npm install -g poseup
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:
docker-compose
, making it difficult to have a single source of truth.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:
poseup run
.exec
.environments
.docker-compose
configuration files from its own poseup.config
-see poseup compose
.docker-compose
commands, hence allowing for a high degree of flexibility -see poseup compose
.poseup
options runs docker-compose
as per your poseup.config
file.poseup run
is a task runner for docker.poseup clean
cleans all non persisted containers.poseup purge
purges volumes, networks, and images from your system.poseup.config
file.poseup
optionsUsage:
$ 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 clean
log
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 help
docker-compose
fileTo 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 help
poseup 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 help
poseup 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 help
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.
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.
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.
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 running poseup clean
and poseup 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 traditional docker-compose
file. Here is where you define your services and their configuration. Having your docker-compose
configuration inside the poseup config will allow you, if desired, to have all environmental differences in a single file, while you can always generate an actual docker-compose
yaml file for any environment from it by dry running poseup compose
.tasks
: object, optional, any number of tasks to be executed via poseup run
. The keys of the task
object 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 running poseup run --list
.primary
: string, the name of the service for which a new ephemeral container will be created in order to run cmd
. If no primary
is specified, cmd
will 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 a primary
service has been specified, poseup will use the services defined on the depends_on
key of the service in the compose
definition by default. If it exists, it will not merge with depends_on
, this way you can restrict or extend the required services for this task with no regard for the compose
definition. If not present and no primary
has 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 on primary
or 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 via services
or the compose
depends_on
- before cmd
is 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 the exec
array 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'] }
]
}
}
});
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:
poseup.config
files and pass them to poseup depending on the one you desire to apply via the --file
flag,poseup.config
that 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
}
}
})
);
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;')
}
}
}
}
})
);
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:
Generated using TypeDoc