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 cleanlogSets logging level. Can be one of silent, trace, debug, info, warn, error.
Example: poseup --log debug clean
fileSets 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
dirSets 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
envAssigns 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 composeRuns 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 helpdocker-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 runA 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 cleanCleans 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 purgeShorthand 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 helpA 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