diff --git a/Makefile b/Makefile index b6889e4287d..5e4a6673f94 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest DOKKU_LIB_ROOT ?= /var/lib/dokku PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/plugins CORE_PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/core-plugins +DOCKER_COMPOSE_VERSION ?= 1.5.0rc2 # If the first argument is "vagrant-dokku"... ifeq (vagrant-dokku,$(firstword $(MAKECMDGOALS))) @@ -22,7 +23,7 @@ else BUILD_STACK_TARGETS = build-in-docker endif -.PHONY: all apt-update install version copyfiles man-db plugins dependencies sshcommand plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku +.PHONY: all apt-update install version copyfiles man-db plugins dependencies sshcommand plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku docker-compose shyaml include tests.mk include deb.mk @@ -77,12 +78,21 @@ plugin-dependencies: plugn plugins: plugn docker dokku plugin:install --core -dependencies: apt-update sshcommand plugn docker help2man man-db +dependencies: apt-update sshcommand plugn docker help2man man-db docker-compose shyaml $(MAKE) -e stack apt-update: apt-get update +shyaml: + apt-get install -qq -y python-yaml + curl -L https://raw.githubusercontent.com/0k/shyaml/master/shyaml > /usr/local/bin/shyaml + chmod +x /usr/local/bin/shyaml + +docker-compose: + curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + help2man: apt-get install -qq -y help2man diff --git a/debian/control b/debian/control index 49d745075d5..09a4885352e 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,6 @@ Section: base Priority: optional Architecture: amd64 Depends: locales, git, make, curl, software-properties-common, docker-engine | docker-engine-cs | lxc-docker, gcc, python-software-properties, man-db, herokuish, sshcommand -Pre-Depends: nginx, dnsutils, ruby, ruby-dev, rubygem-rack, rubygem-rack-protection, rubygem-sinatra, rubygem-tilt, apparmor, cgroupfs-mount | cgroup-lite, plugn, sudo +Pre-Depends: nginx, dnsutils, ruby, ruby-dev, rubygem-rack, rubygem-rack-protection, rubygem-sinatra, rubygem-tilt, apparmor, cgroupfs-mount | cgroup-lite, plugn, sudo, python-yaml Maintainer: Jose Diaz-Gonzalez Description: Docker powered mini-Heroku in around 100 lines of Bash diff --git a/docs/application-deployment.md b/docs/application-deployment.md index c01a5e48899..8fecd42c4f5 100644 --- a/docs/application-deployment.md +++ b/docs/application-deployment.md @@ -192,4 +192,4 @@ See the [zero-downtime deploy documentation](/dokku/checks-examples/). ## Image tagging -See the [image tagging documentation](/dokku/deployment/images). +See the [image tagging documentation](/dokku/deployment/images/). diff --git a/docs/deployment/docker-compose.md b/docs/deployment/docker-compose.md new file mode 100644 index 00000000000..c7edc02d62d --- /dev/null +++ b/docs/deployment/docker-compose.md @@ -0,0 +1,53 @@ +# Dockerfile Deployment + +> New as of 0.4.3 + +As an extension of Dockerfile-based deployment, you can also deploy an application that uses `docker-compose`. This is useful as an alternative to running a process manager inside of a `Dockerfile`-based container, and should be considered the canonical way to deploy multiple processes when using a `Dockerfile` directly. + +To deploy an application via `docker-compose`, your repository will need the following files: + +- `Dockerfile` +- `docker-compose.yml` + +To use `docker-compose` for deployment, commit a valid `Dockerfile` **and** `docker-compose.yml` to the root of your repository and push the repository to your Dokku installation. If these files are detected, Dokku will default to using it to construct containers **except** in the following two cases: + +- The application has a `BUILDPACK_URL` environment variable set via the `dokku config:set` command or in a committed `.env` file. In this case, Dokku will use your specified buildpack. +- The application has a `.buildpacks` file in the root of the repository. In this case, Dokku will use your specified buildpack(s). + +At this time, you cannot fallback to using `Dockerfile` only deployments. + +## Web processes + +At this time, you may only have **one** web process proxied by nginx. Having any other processes will require manual configuration of your nginx configuration. + +The web process is noted as either the `app` or `web` stanza in your `docker-compose.yml` file, with the `web` stanza having precedence. + +Your application *must* have either an `app` or `web` stanza or the `docker-compose` deployment will fail. + +## Exposed ports + +By default, Dokku will extract the first `EXPOSE` tcp port and use said port with nginx to proxy your app to that port. For applications that have multiple ports exposed, you may override this port via the following command: + +```shell +# replace APP with the name of your application +dokku config:set APP DOKKU_DOCKERFILE_PORT=8000 +``` + +Dokku will not expose other ports on your application without a [custom docker-option](/dokku/configuration/docker-options/). + +If you do not have a port explicitly exposed, Dokku will automatically expose port `5000` for your application. + +## Dependent images + +Dokku will automatically use the images specified for each process-type specified in a `docker-compose.yml` file, and will fall back to the image created by either the `app` or `web` process-type. Non-primary process types will be scaled to 0 by default. + +## Limitations + +`docker-compose` support has a few known limitations. The below is not considered exhaustive, and may change over time. + +- Container `links` do not work. When Dokku starts containers, the containers are named pseudo-randomly, and due to how zero-downtime deployments work, we cannot guarantee that any started links will start properly. Docker also cannot link already started containers, and thus this functionality is currently unused. For datastores, please use the [official datastore plugins](/dokku/plugins/#official-plugins-beta). +- Dokku uses a special `Procfile.compose` file to maintain a mapping of the process type to docker image. Please avoid using this file for other purposes. + +## Process scaling + +Process scaling works the same as it does for `buildpack` deployments. See the [process management documentation](/dokku/process-management/). diff --git a/docs/process-management.md b/docs/process-management.md index 120a7269f24..0eb5bdd2c28 100644 --- a/docs/process-management.md +++ b/docs/process-management.md @@ -17,7 +17,7 @@ ps:stop Stop app container(s) ## Scaling -Dokku allows you to run multiple process types at different container counts. For example, if you had an app that contained 1 web app listener and 1 background job processor, dokku can, spin up 1 container for each process type defined in the Procfile. *By default, dokku will only start a single web process (if defined.)* However, if you wanted, for example, 2 job processors running simultaneously, you can modify this behavior in one of the following ways. +Dokku allows you to run multiple process types at different container counts. For example, if you had an app that contained 1 web app listener and 1 background job processor, dokku can, spin up 1 container for each process type defined in the `Procfile` or `docker-compose.yml` file. *By default, dokku will only start a single web process (if defined.)* However, if you wanted, for example, 2 job processors running simultaneously, you can modify this behavior in one of the following ways. ### DOKKU_SCALE file diff --git a/docs/template.html b/docs/template.html index 292717f7d53..1618624ad85 100644 --- a/docs/template.html +++ b/docs/template.html @@ -70,6 +70,7 @@

Deploying an Application Dockerfile Deployment + Docker Compose Deployment Image Tagging Remote Commands One Off Processes diff --git a/dokku b/dokku index 72084afbf75..f8f1c84170e 100755 --- a/dokku +++ b/dokku @@ -100,6 +100,17 @@ case "$1" in PROC_TYPE=${TRIM%%=*} PROC_COUNT=${TRIM#*=} CONTAINER_INDEX=1 + PROC_IMAGE=$IMAGE + COMPOSE_PROCFILE_DIR="/tmp/$APP" + COMPOSE_PROCFILE="$COMPOSE_PROCFILE_DIR/Procfile.compose" + + mkdir -p "$COMPOSE_PROCFILE_DIR" + copy_from_image "$IMAGE" "/tmp/Procfile.compose" "$COMPOSE_PROCFILE_DIR" 2>/dev/null || true + if [[ -f "$COMPOSE_PROCFILE" ]]; then + while read line || [[ -n "$line" ]]; do + [[ "$line" == $PROC_TYPE* ]] && PROC_IMAGE=$(echo $line | cut -d' ' -f2) && break + done < "$COMPOSE_PROCFILE" + fi while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do id=""; port=""; ipaddr="" @@ -123,15 +134,15 @@ case "$1" in if [[ "$PROC_TYPE" == "web" ]]; then port=${DOKKU_DOCKERFILE_PORT:=5000} if [[ "$BIND_EXTERNAL" = "true" ]]; then - id=$(docker run -d -p $port -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + id=$(docker run -d -p $port -e PORT=$port $DOCKER_ARGS $PROC_IMAGE $START_CMD) port=$(docker port $id $port | sed 's/[0-9.]*://') ipaddr=127.0.0.1 else - id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + id=$(docker run -d -e PORT=$port $DOCKER_ARGS $PROC_IMAGE $START_CMD) ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id) fi else - id=$(docker run -d $DOCKER_ARGS $IMAGE $START_CMD) + id=$(docker run -d $DOCKER_ARGS $PROC_IMAGE $START_CMD) fi # if we can't post-deploy successfully, kill new container diff --git a/plugins/00_dokku-standard/commands b/plugins/00_dokku-standard/commands index b2dcf2a08f6..ba63724f673 100755 --- a/plugins/00_dokku-standard/commands +++ b/plugins/00_dokku-standard/commands @@ -8,6 +8,7 @@ source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" case "$1" in build) APP="$2"; IMAGE_SOURCE_TYPE="$3"; TMP_WORK_DIR="$4"; IMAGE=$(get_app_image_name $APP) + APP_DIR="$DOKKU_ROOT/$APP/app" verify_app_name "$APP" CACHE_DIR="$DOKKU_ROOT/$APP/cache" @@ -46,6 +47,51 @@ case "$1" in plugn trigger post-build-dockerfile "$APP" ;; + docker-compose) + # extract first port from docker-compose.yml + COMPOSE_PORT=$(shyaml get-value app.ports.0 < docker-compose.yml 2> /dev/null || true) + [[ ! -n "$COMPOSE_PORT" ]] && COMPOSE_PORT=$(shyaml get-value web.ports.0 < docker-compose.yml 2> /dev/null || true) + [[ -n "$COMPOSE_PORT" ]] && config_set --no-restart $APP DOKKU_COMPOSE_PORT=$COMPOSE_PORT + plugn trigger pre-build-docker-compose "$APP" + + COMPOSE_ARGS=$(: | plugn trigger docker-compose-args-build $APP) + dokku_log_verbose_quiet "Building docker images, it may take some time ..." + cd $TMP_WORK_DIR && docker-compose $COMPOSE_ARGS --project-name "$APP" build > /dev/null + + COMPOSE_ARGS=$(: | plugn trigger docker-compose-args-pull $APP) + dokku_log_verbose_quiet "Pulling service images, it may take some time ..." + cd $TMP_WORK_DIR && docker-compose $COMPOSE_ARGS --project-name "$APP" pull > /dev/null + + web_stanza_type=$(shyaml get-type web < docker-compose.yml 2> /dev/null || true) + app_stanza_type=$(shyaml get-type app < docker-compose.yml 2> /dev/null || true) + if [[ $web_stanza_type == "struct" ]]; then + docker tag -f "$APP"_web "$IMAGE" + elif [[ $app_stanza_type == "struct" ]]; then + docker tag -f "$APP"_app "$IMAGE" + else + dokku_log_fail "Could not detect app or web stanza in docker-compose.yml" + fi + + # keep track of all the images + process_file="Procfile.compose" + touch "$process_file" + for key in $(shyaml keys < docker-compose.yml); do + image_name=$(shyaml get-value $key.image < docker-compose.yml 2> /dev/null || echo "${APP}_${key}"); + image_tag="$IMAGE-$key" + docker tag -f "$image_name" "$image_tag" + echo "$key: $image_tag" >> "$process_file" + done + + # add a compose procfile + id=$(docker run -i -a stdin $IMAGE /bin/bash -c "cat > /tmp/$process_file" < "$process_file") + test "$(docker wait $id)" -eq 0 + docker commit $id $IMAGE > /dev/null + docker stop $id > /dev/null 2>&1 || true + docker kill $id > /dev/null 2>&1 || true + + plugn trigger post-build-docker-compose "$APP" + ;; + *) dokku_log_fail "Building image source type $IMAGE_SOURCE_TYPE not supported!" ;; @@ -78,6 +124,12 @@ case "$1" in plugn trigger post-release-dockerfile "$APP" "$IMAGE_TAG" ;; + docker-compose) + # buildstep plugins don't necessarily make sense for docker-compose. call the new breed!!! + plugn trigger pre-release-docker-compose "$APP" "$IMAGE_TAG" + plugn trigger post-release-docker-compose "$APP" "$IMAGE_TAG" + ;; + *) dokku_log_fail "Releasing image source type $IMAGE_SOURCE_TYPE not supported!" ;; diff --git a/plugins/00_dokku-standard/compose-args-build b/plugins/00_dokku-standard/compose-args-build new file mode 100755 index 00000000000..dc6e173107e --- /dev/null +++ b/plugins/00_dokku-standard/compose-args-build @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +STDIN=$(cat) +APP="$1" + +echo "$STDIN -p $APP" diff --git a/plugins/00_dokku-standard/compose-args-run b/plugins/00_dokku-standard/compose-args-run new file mode 100755 index 00000000000..dc6e173107e --- /dev/null +++ b/plugins/00_dokku-standard/compose-args-run @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +STDIN=$(cat) +APP="$1" + +echo "$STDIN -p $APP" diff --git a/plugins/git/commands b/plugins/git/commands index 783813f12f4..0116d84b10c 100755 --- a/plugins/git/commands +++ b/plugins/git/commands @@ -20,13 +20,14 @@ git_build_app_repo() { git submodule update --init --recursive &> /dev/null find -name .git -prune -exec rm -rf {} \; > /dev/null - if [[ -f Dockerfile ]] && [[ "$([[ -f .env ]] && grep -q BUILDPACK_URL .env; echo $?)" != "0" ]] && [[ ! -f ".buildpacks" ]]; then - plugn trigger pre-receive-app "$APP" "dockerfile" "$TMP_WORK_DIR" - dokku receive "$APP" "dockerfile" "$TMP_WORK_DIR" | sed -u "s/^/"$'\e[1G'"/" - else - plugn trigger pre-receive-app "$APP" "herokuish" "$TMP_WORK_DIR" - dokku receive "$APP" "herokuish" "$TMP_WORK_DIR" | sed -u "s/^/"$'\e[1G'"/" + local IMAGE_SOURCE_TYPE="herokuish" + if [[ "$([[ -f .env ]] && grep -q BUILDPACK_URL .env; echo $?)" != "0" ]] && [[ ! -f ".buildpacks" ]]; then + [[ -f Dockerfile ]] && IMAGE_SOURCE_TYPE="dockerfile" + [[ -f Dockerfile ]] && [[ -f docker-compose.yml ]] && IMAGE_SOURCE_TYPE="docker-compose" fi + + plugn trigger pre-receive-app "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR" + dokku receive "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR" | sed -u "s/^/"$'\e[1G'"/" } case "$1" in diff --git a/plugins/ps/functions b/plugins/ps/functions index eafc1991690..2a0a4a2b349 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -21,17 +21,21 @@ generate_scale_file() { trap 'rm -rf "$TMP_WORK_DIR" > /dev/null' RETURN copy_from_image "$IMAGE" "/app/Procfile" "$TMP_WORK_DIR" 2>/dev/null || true + copy_from_image "$IMAGE" "/tmp/Procfile.compose" "$TMP_WORK_DIR" 2>/dev/null || true local PROCFILE="$TMP_WORK_DIR/Procfile" - if [[ ! -e "$PROCFILE" ]]; then + local COMPOSE_PROCFILE="$TMP_WORK_DIR/Procfile.compose" + if [[ ! -e "$PROCFILE" ]] && [[ ! -e "$COMPOSE_PROCFILE" ]]; then echo "web=1" >> $DOKKU_SCALE_FILE else + DOKKU_PROCFILE="$PROCFILE" + [[ -e "$COMPOSE_PROCFILE" ]] && DOKKU_PROCFILE="$COMPOSE_PROCFILE" while read line || [[ -n "$line" ]]; do [[ -z "$line" ]] && continue NAME=${line%%:*} NUM_PROCS=0 [[ "$NAME" == "web" ]] && NUM_PROCS=1 echo "$NAME=$NUM_PROCS" >> $DOKKU_SCALE_FILE - done < "$PROCFILE" + done < "$DOKKU_PROCFILE" fi dokku_log_info1_quiet "New DOKKU_SCALE file generated" else