Code taken from Hercules CI repo
- renamed to Arion - minor changes - readme WIP
This commit is contained in:
commit
dc294b034e
9 changed files with 456 additions and 0 deletions
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
# Run docker-compose without images with Nix
|
||||||
|
|
||||||
|
*Wait, what?*
|
||||||
|
|
||||||
|
With Arion you can fire up containers without creating images for each
|
||||||
|
service. Instead, it uses a mostly empty image, sort of like a base
|
||||||
|
image, and makes the host's Nix store available in the container,
|
||||||
|
allowing the container to run programs without having to re-package
|
||||||
|
them into a docker image.
|
||||||
|
|
||||||
|
Arion is configured using Nix with modules, like those in
|
||||||
|
NixOS. Similar to `docker-compose` it can therefore combine
|
||||||
|
configuration from multiple files. For managing the network and
|
||||||
|
containers it delegates to the `docker-compose` command.
|
||||||
|
|
||||||
|
# Project Status
|
||||||
|
|
||||||
|
This project was born out of a process supervision need for local
|
||||||
|
development environments while working
|
||||||
|
on [Hercules CI](https://www.hercules-ci.com). (It was also born out
|
||||||
|
of ancient Greek deities disguised as horses. More on that later.)
|
||||||
|
|
||||||
|
We don't use it to deploy to 'real' environments and we have no plans
|
||||||
|
to do so in the future.
|
||||||
|
|
||||||
|
If you do want to use Arion for 'real' environments, you'll probably
|
||||||
|
want to either build images or manage garbage collection roots if you
|
||||||
|
control the deployment host. Either of these has yet to be
|
||||||
|
implemented.
|
||||||
|
|
||||||
|
# How do I use Arion?
|
||||||
|
|
||||||
|
The command line inherits most `docker-compose` commands. These
|
||||||
|
examples assume you're in this repo's `examples/` directory.
|
||||||
|
|
||||||
|
TODO examples
|
||||||
|
|
||||||
|
# Why "Arion"?
|
||||||
|
|
||||||
|
Arion comes from Greek mythology. Poseidon, the god of ~Docker~ the
|
||||||
|
seas had his eye on Demeter. Demeter tried to trick him by disguising
|
||||||
|
as a horse, but Poseidon saw through the deception and they had Arion.
|
||||||
|
|
||||||
|
So Arion is a super fast divine horse; the result of some weird
|
||||||
|
mixing. Also it talks.
|
||||||
|
|
||||||
|
(And we feel morally obliged to name our stuff after Greek mythology)
|
26
default.nix
Normal file
26
default.nix
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (pkgs) lib stdenv;
|
||||||
|
|
||||||
|
arion = stdenv.mkDerivation {
|
||||||
|
name = "arion";
|
||||||
|
src = ./src;
|
||||||
|
unpackPhase = "";
|
||||||
|
buildPhase = "";
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin $out/share/arion
|
||||||
|
cp -a nix $out/share/arion/
|
||||||
|
cp -a arion-image $out/share/arion/
|
||||||
|
substitute arion $out/bin/arion \
|
||||||
|
--subst-var-by path ${lib.makeBinPath [pkgs.jq pkgs.coreutils]} \
|
||||||
|
--subst-var-by nix_dir $out/share/arion/nix \
|
||||||
|
;
|
||||||
|
chmod a+x $out/bin/arion
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit arion;
|
||||||
|
}
|
199
src/arion
Executable file
199
src/arion
Executable file
|
@ -0,0 +1,199 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH="$PATH:@path@"
|
||||||
|
|
||||||
|
nix_dir="@nix_dir@"
|
||||||
|
docker_compose_args=()
|
||||||
|
files=()
|
||||||
|
command="docker-compose"
|
||||||
|
pkgs_argument="./arion-pkgs.nix"
|
||||||
|
|
||||||
|
debug() {
|
||||||
|
# echo "$@"
|
||||||
|
:
|
||||||
|
}
|
||||||
|
|
||||||
|
while test $# != 0; do
|
||||||
|
case "$1" in
|
||||||
|
-f|--file)
|
||||||
|
shift
|
||||||
|
files+=("$1")
|
||||||
|
;;
|
||||||
|
-f*)
|
||||||
|
files+=("${1/#-f/}")
|
||||||
|
;;
|
||||||
|
--file=*)
|
||||||
|
files+=("${1/#--file=}")
|
||||||
|
;;
|
||||||
|
--pkgs)
|
||||||
|
shift
|
||||||
|
pkgs_argument="$1"
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
command="help"
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
cat)
|
||||||
|
command="$1"
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
repl)
|
||||||
|
command="$1"
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
docker-compose)
|
||||||
|
command="docker-compose"
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
while test $# != 0; do
|
||||||
|
docker_compose_args+=("$1")
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
help)
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Arion wraps your system's docker-compose, providing a NixOps-like
|
||||||
|
experience for simple container deployments.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
arion up|logs|... - execute docker-compose commands
|
||||||
|
arion cat - display raw docker-compose.yaml
|
||||||
|
arion config - validate and display the config file
|
||||||
|
arion repl - explore the config interactively
|
||||||
|
arion help
|
||||||
|
arion docker-compose help
|
||||||
|
arion docker-compose help up|logs|...
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ${#files[@]} == 0 ]]; then
|
||||||
|
files=("./arion-compose.nix")
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
debug docker_compose_args: "${docker_compose_args[@]}"
|
||||||
|
debug files: "${files[@]}"
|
||||||
|
|
||||||
|
docker_compose_yaml=.tmp-nix-docker-compose-$$-$RANDOM.yaml
|
||||||
|
cleanup() {
|
||||||
|
rm -f $docker_compose_yaml
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
modules="["
|
||||||
|
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
case "$file" in
|
||||||
|
/*)
|
||||||
|
modules="$modules (/. + $(printf '"%q"' $file))"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
modules="$modules (./. + $(printf '"/%q"' $file))"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
modules="$modules ]"
|
||||||
|
|
||||||
|
debug modules: $modules
|
||||||
|
|
||||||
|
old_IFS="$IFS"
|
||||||
|
IFS=""
|
||||||
|
args=(
|
||||||
|
)
|
||||||
|
IFS="$old_IFS"
|
||||||
|
for arg in "${args[@]}"; do
|
||||||
|
echo arg: $arg
|
||||||
|
done
|
||||||
|
|
||||||
|
do_build() {
|
||||||
|
echo 1>&2 "Building configuration..."
|
||||||
|
nix-build \
|
||||||
|
"$nix_dir/eval-docker-compose.nix" \
|
||||||
|
--out-link $docker_compose_yaml \
|
||||||
|
--argstr uid "$UID" \
|
||||||
|
--arg modules "$modules" \
|
||||||
|
--arg pkgs "$pkgs_argument" \
|
||||||
|
--show-trace \
|
||||||
|
--attr 'config.build.dockerComposeYaml' \
|
||||||
|
>/dev/null ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_repl() {
|
||||||
|
# nix repl doesn't autocall its <FILES> arguments
|
||||||
|
# so we improvise. We need a file in this directory
|
||||||
|
# to make sure that all paths are as expected :(
|
||||||
|
trap do_repl_cleanup EXIT;
|
||||||
|
|
||||||
|
REPL_TMP=.tmp-repl-$$-$RANDOM
|
||||||
|
cat <<EOF
|
||||||
|
Launch a repl for you, using a temporary file: $REPL_TMP.
|
||||||
|
|
||||||
|
This loads the configuration from the modules
|
||||||
|
$files
|
||||||
|
|
||||||
|
To get started:
|
||||||
|
|
||||||
|
To see deployment-wide configuration
|
||||||
|
type config. and hit TAB
|
||||||
|
To see the services
|
||||||
|
type config.docker-compose.services TAB or ENTER
|
||||||
|
To bring the top-level Nixpkgs attributes into scope
|
||||||
|
type :a (config._module.args.pkgs) // { inherit config; }
|
||||||
|
|
||||||
|
EOF
|
||||||
|
cat >"$REPL_TMP" <<EOF
|
||||||
|
import $nix_dir/eval-docker-compose.nix {
|
||||||
|
uid = "$UID";
|
||||||
|
modules = $modules;
|
||||||
|
pkgs = $pkgs_argument;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
nix repl \
|
||||||
|
"$REPL_TMP" \
|
||||||
|
;
|
||||||
|
}
|
||||||
|
do_repl_cleanup() {
|
||||||
|
rm -f $REPL_TMP
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
cat)
|
||||||
|
do_build
|
||||||
|
cat $docker_compose_yaml | jq .
|
||||||
|
;;
|
||||||
|
repl)
|
||||||
|
do_repl
|
||||||
|
;;
|
||||||
|
docker-compose)
|
||||||
|
if [[ ${#docker_compose_args[@]} != 0
|
||||||
|
&& ${docker_compose_args[0]} != "help"
|
||||||
|
&& ${docker_compose_args[0]} != "version"
|
||||||
|
]]; then
|
||||||
|
do_build
|
||||||
|
fi
|
||||||
|
docker-compose -f $docker_compose_yaml "${docker_compose_args[@]}"
|
||||||
|
;;
|
||||||
|
esac
|
2
src/arion-image/Dockerfile
Normal file
2
src/arion-image/Dockerfile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
FROM scratch
|
||||||
|
COPY passwd /etc/passwd
|
2
src/arion-image/passwd
Normal file
2
src/arion-image/passwd
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
root:x:0:0:System administrator:/root:/bin/sh
|
||||||
|
nobody:x:65534:65534:Unprivileged account (don't use!):/var/empty:/run/current-system/sw/bin/nologin
|
49
src/nix/docker-compose-module.nix
Normal file
49
src/nix/docker-compose-module.nix
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{ pkgs, uid, lib, config, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
evalService = name: modules:
|
||||||
|
let
|
||||||
|
composite = lib.evalModules {
|
||||||
|
check = true;
|
||||||
|
modules = builtinModules ++ modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
builtinModules = [
|
||||||
|
argsModule
|
||||||
|
./service.nix
|
||||||
|
./service-host-store.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
argsModule = {
|
||||||
|
_file = ./docker-compose-module.nix;
|
||||||
|
key = ./docker-compose-module.nix;
|
||||||
|
config._module.args.pkgs = lib.mkForce pkgs;
|
||||||
|
config._module.args.uid = uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
composite.config.build.service;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
build.dockerComposeYaml = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
};
|
||||||
|
docker-compose.raw = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
};
|
||||||
|
docker-compose.services = lib.mkOption {
|
||||||
|
default = {};
|
||||||
|
type = with lib.types; attrsOf (coercedTo unspecified (a: [a]) (listOf unspecified));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" (builtins.toJSON (config.docker-compose.raw));
|
||||||
|
|
||||||
|
docker-compose.raw = {
|
||||||
|
version = "3";
|
||||||
|
services = lib.mapAttrs evalService config.docker-compose.services;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
33
src/nix/eval-docker-compose.nix
Normal file
33
src/nix/eval-docker-compose.nix
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{ modules ? [], uid ? 0, pkgs }:
|
||||||
|
|
||||||
|
let _pkgs = pkgs;
|
||||||
|
in
|
||||||
|
let
|
||||||
|
pkgs = if builtins.typeOf _pkgs == "path"
|
||||||
|
then import _pkgs
|
||||||
|
else if builtins.typeOf _pkgs == "set"
|
||||||
|
then _pkgs
|
||||||
|
else builtins.abort "The pkgs argument must be an attribute set or a path to an attribute set.";
|
||||||
|
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
|
composite = lib.evalModules {
|
||||||
|
check = true;
|
||||||
|
modules = builtinModules ++ modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
builtinModules = [
|
||||||
|
argsModule
|
||||||
|
./docker-compose-module.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
argsModule = {
|
||||||
|
_file = ./eval-docker-compose.nix;
|
||||||
|
key = ./eval-docker-compose.nix;
|
||||||
|
config._module.args.pkgs = lib.mkIf (pkgs != null) (lib.mkForce pkgs);
|
||||||
|
config._module.args.uid = uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
# Typically you need composite.config.build.dockerComposeYaml
|
||||||
|
composite
|
23
src/nix/service-host-store.nix
Normal file
23
src/nix/service-host-store.nix
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Bind-mounts the host store
|
||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types mkIf;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
service.useHostStore = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Bind mounts the host store if enabled, avoiding copying.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = mkIf config.service.useHostStore {
|
||||||
|
service.image = "arion-base";
|
||||||
|
service.build.context = "${../arion-image}";
|
||||||
|
service.volumes = [
|
||||||
|
"/nix/store:/nix/store"
|
||||||
|
"/bin/sh:/bin/sh"
|
||||||
|
"/usr/bin/env:/usr/bin/env"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
74
src/nix/service.nix
Normal file
74
src/nix/service.nix
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
inherit (types) listOf nullOr attrsOf string either;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
service.volumes = mkOption {
|
||||||
|
type = listOf types.unspecified;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
service.build.context = mkOption {
|
||||||
|
type = nullOr string;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
service.environment = mkOption {
|
||||||
|
type = attrsOf string;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
service.image = mkOption {
|
||||||
|
type = string;
|
||||||
|
};
|
||||||
|
service.command = mkOption {
|
||||||
|
type = nullOr types.unspecified;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
service.depends_on = mkOption {
|
||||||
|
type = listOf string;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
service.restart = mkOption {
|
||||||
|
type = nullOr string;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
service.ports = mkOption {
|
||||||
|
type = listOf types.unspecified;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
Expose ports on host. "host:container" or structured.
|
||||||
|
See https://docs.docker.com/compose/compose-file/#ports
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
service.expose = mkOption {
|
||||||
|
type = listOf string;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
build.service = mkOption {
|
||||||
|
type = attrsOf types.unspecified;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.build.service = {
|
||||||
|
inherit (config.service)
|
||||||
|
volumes
|
||||||
|
environment
|
||||||
|
image
|
||||||
|
;
|
||||||
|
} // lib.optionalAttrs (config.service.build.context != null) {
|
||||||
|
inherit (config.service) build;
|
||||||
|
} // lib.optionalAttrs (config.service.command != null) {
|
||||||
|
inherit (config.service) command;
|
||||||
|
} // lib.optionalAttrs (config.service.depends_on != []) {
|
||||||
|
inherit (config.service) depends_on;
|
||||||
|
} // lib.optionalAttrs (config.service.restart != null) {
|
||||||
|
inherit (config.service) restart;
|
||||||
|
} // lib.optionalAttrs (config.service.ports != []) {
|
||||||
|
inherit (config.service) ports;
|
||||||
|
} // lib.optionalAttrs (config.service.expose != []) {
|
||||||
|
inherit (config.service) expose;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue