diff --git a/docs/nginx.md b/docs/nginx.md index 712770abe..a27347ab1 100644 --- a/docs/nginx.md +++ b/docs/nginx.md @@ -174,7 +174,7 @@ You can also use the built-in `domains` plugin to handle: ### Disabling VHOSTS -If desired, it is possible to disable vhosts by setting the `NO_VHOST` environment variable: +If desired, it is possible to disable vhosts by setting the environment variable `NO_VHOST=1`: ```shell dokku config:set myapp NO_VHOST=1 diff --git a/plugins/00_dokku-standard/commands b/plugins/00_dokku-standard/commands index 051956413..4e3eb5141 100755 --- a/plugins/00_dokku-standard/commands +++ b/plugins/00_dokku-standard/commands @@ -2,6 +2,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" case "$1" in build) @@ -189,9 +190,7 @@ case "$1" in SCHEME="https" fi - if [[ -f "$DOKKU_ROOT/VHOST" ]] && [[ "$NO_VHOST" != "1" ]]; then - echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" - else + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE") (container)" done @@ -205,6 +204,8 @@ case "$1" in if [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then echo "https://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_SSL_PORT (nginx-ssl)" fi + else + echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" fi ;; diff --git a/plugins/common/functions b/plugins/common/functions index 2e6aa2891..69e4c6315 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -331,7 +331,7 @@ docker_cleanup() { docker rmi $(docker images -f 'dangling=true' -q) &> /dev/null & } -get_open_port() { +get_available_port() { while true; do local port=$(shuf -i 1025-65535 -n 1) if ! nc -z 0.0.0.0 $port; then diff --git a/plugins/config/functions b/plugins/config/functions index 6dca410bb..6e727623d 100644 --- a/plugins/config/functions +++ b/plugins/config/functions @@ -214,6 +214,7 @@ config_unset() { ENV_TEMP=$(echo -e "${ENV_TEMP}" | sed "/^export $var=/ d") config_write "$ENV_TEMP" + [[ "$var" == "NO_VHOST" ]] && config_set --no-restart $APP NO_VHOST=0 && DOKKU_CONFIG_RESTART=true done if [[ "$DOKKU_CONFIG_RESTART" == "true" ]]; then diff --git a/plugins/domains/commands b/plugins/domains/commands index 3788efb8e..f67f7648e 100755 --- a/plugins/domains/commands +++ b/plugins/domains/commands @@ -1,7 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" @@ -53,7 +52,7 @@ case "$1" in fi if [[ "$VHOST" =~ $RE_IPV4 ]] || [[ "$VHOST" =~ $RE_IPV6 ]]; then dokku_log_info2 "unsupported vhost config found. disabling vhost support" - config_set --no-restart $APP NO_VHOST=1 + disable_app_vhost $APP --no-restart $APP else if [[ -f "$DOKKU_ROOT/VHOST" ]]; then dokku_log_info1 "Creating new $VHOST_PATH..." diff --git a/plugins/domains/functions b/plugins/domains/functions index 501f51908..4b5da1790 100755 --- a/plugins/domains/functions +++ b/plugins/domains/functions @@ -3,9 +3,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" get_app_domains() { - local APP=$1; local VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" + local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" verify_app_name $APP - if [[ -f "$VHOST_FILE" ]]; then - cat "$VHOST_FILE" + if [[ -f "$APP_VHOST_FILE" ]];then + cat "$APP_VHOST_FILE" fi } diff --git a/plugins/nginx-vhosts/bind-external-ip b/plugins/nginx-vhosts/bind-external-ip index b5f5c01d5..324d82852 100755 --- a/plugins/nginx-vhosts/bind-external-ip +++ b/plugins/nginx-vhosts/bind-external-ip @@ -1,41 +1,13 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" APP="$1" -NO_VHOST=$(config_get $APP NO_VHOST || true) -RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}" - -RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7:: -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 -RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 -RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: -RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index) -RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) -RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 - - -# Ensure the ip address continues to the end of the line -# Fixes using a wildcard dns service such as xip.io which allows for *..xip.io -RE_IPV4="${RE_IPV4}\$" -RE_IPV6="${RE_IPV6}\$" - -[[ -f "$DOKKU_ROOT/VHOST" ]] && GLOBAL_VHOST=$(< "$DOKKU_ROOT/VHOST") - -if [[ -n "$NO_VHOST" ]]; then +if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then echo true # bind to external ip. VHOST is disabled -elif [[ "$GLOBAL_VHOST" =~ $RE_IPV4 ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV6 ]]; then - echo true # bind to external ip. GLOBAL_VHOST is somehow an IPv4 or IPv6 address -elif [[ -z "$GLOBAL_VHOST" ]] && [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - echo true # bind to external ip. no GLOBAL_VHOST and no app vhost -elif [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - echo false # bind to docker ip. this app has a vhost defined +elif [[ "$(is_global_vhost_enabled $APP)" == "false" ]] && [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + echo true # bind to external ip. no global vhost or global vhost is an ip else echo false fi diff --git a/plugins/nginx-vhosts/commands b/plugins/nginx-vhosts/commands index d90349d79..44e03a27d 100755 --- a/plugins/nginx-vhosts/commands +++ b/plugins/nginx-vhosts/commands @@ -35,12 +35,11 @@ case "$1" in verify_app_name "$2" APP="$2" - eval "$(config_export app $APP)" - if [[ -z "$NO_VHOST" ]]; then - config_set --no-restart $APP NO_NGINX=1 - config_set $APP NO_VHOST=1 - elif [[ -z "$NO_NGINX" ]]; then - config_set --no-restart $APP NO_NGINX=1 + if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then + config_set --no-restart $APP DOKKU_NO_NGINX=1 + disable_app_vhost $APP + elif [[ "$(is_app_nginx_enabled $APP)" == "true" ]]; then + config_set --no-restart $APP DOKKU_NO_NGINX=1 nginx_build_config $APP else dokku_log_info1 "nginx is already disable for app ($APP)" @@ -52,10 +51,9 @@ case "$1" in verify_app_name "$2" APP="$2" - eval "$(config_export app $APP)" - if [[ -n "$NO_NGINX" ]]; then - config_unset --no-restart $APP NO_NGINX - unset NO_NGINX + if [[ "$(is_app_nginx_enabled $APP)" == "true" ]]; then + config_unset --no-restart $APP DOKKU_NO_NGINX + unset DOKKU_NO_NGINX nginx_build_config $APP else dokku_log_info1 "nginx is already enabled for app ($APP)" diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 8b8979885..9221b77dc 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -5,6 +5,83 @@ source "$PLUGIN_AVAILABLE_PATH/certs/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" +is_app_nginx_enabled() { + local APP="$1" + verify_app_name "$APP" + + local DOKKU_NO_NGINX=$(config_get $APP DOKKU_NO_NGINX) + if [[ -z "$DOKKU_NO_NGINX" ]]; then + echo true + else + echo false + fi +} + +is_global_vhost_enabled() { + local GLOBAL_VHOST_FILE="$DOKKU_ROOT/VHOST" + local GLOBAL_VHOST_ENABLED=true + [[ -f "$GLOBAL_VHOST_FILE" ]] && local GLOBAL_VHOST=$(< "$GLOBAL_VHOST_FILE") + + local RE_IPV4="([0-9]{1,3}[\.]){3}[0-9]{1,3}" + + local RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" # TEST: 1:2:3:4:5:6:7:8 + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|" # TEST: 1:: 1:2:3:4:5:6:7:: + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" # TEST: 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" # TEST: 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" # TEST: 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" # TEST: 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" # TEST: 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + local RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" # TEST: 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + local RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|" # TEST: ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + local RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|" # TEST: fe08::7:8%eth0 fe08::7:8%1 (link-local IPv6 addresses with zone index) + local RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|" # TEST: ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + local RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}" # TEST: 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 + + + # Ensure the ip address continues to the end of the line + # Fixes using a wildcard dns service such as xip.io which allows for *..xip.io + local RE_IPV4="${RE_IPV4}\$" + local RE_IPV6="${RE_IPV6}\$" + + if [[ -z "$GLOBAL_VHOST" ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV4 ]] || [[ "$GLOBAL_VHOST" =~ $RE_IPV6 ]]; then + local GLOBAL_VHOST_ENABLED=false + fi + echo $GLOBAL_VHOST_ENABLED +} + +is_app_vhost_enabled() { + local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" + verify_app_name $APP + + local NO_VHOST=$(config_get $APP NO_VHOST) + local APP_VHOST_ENABLED=true + + if [[ "$NO_VHOST" == "1" ]]; then + local APP_VHOST_ENABLED=false + elif [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]] && [[ ! -f "$APP_VHOST_FILE" ]] && [[ "$NO_VHOST" != "0" ]]; then + local APP_VHOST_ENABLED=false + fi + + echo $APP_VHOST_ENABLED +} + +disable_app_vhost() { + local APP=$1; local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" + verify_app_name $APP + + if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" + rm "$DOKKU_ROOT/$APP/VHOST" + fi + if [[ -f "$DOKKU_ROOT/$APP/URLS" ]]; then + dokku_log_info1 "VHOST support disabled, deleting $APP/URLS" + rm "$DOKKU_ROOT/$APP/URLS" + fi + + [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 + config_set $CONFIG_SET_ARGS $APP NO_VHOST=1 +} + validate_nginx() { set +e sudo /usr/sbin/nginx -t > /dev/null 2>&1 @@ -40,7 +117,7 @@ nginx_build_config() { eval "$(config_export global)" eval "$(config_export app $APP)" - if [[ ! -n "$NO_NGINX" ]]; then + if [[ ! -n "$DOKKU_NO_NGINX" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then shopt -s nullglob for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do @@ -63,21 +140,13 @@ nginx_build_config() { NGINX_CONF=$(mktemp -t "nginx.conf.XXXXXX") SCHEME="http" - if [[ -n "$NO_VHOST" ]]; then - if [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then - dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" - rm "$DOKKU_ROOT/$APP/VHOST" - fi - if [[ -f "$DOKKU_ROOT/$APP/URLS" ]]; then - dokku_log_info1 "VHOST support disabled, deleting $APP/URLS" - rm "$DOKKU_ROOT/$APP/URLS" - fi - fi + # if the app has is not vhost enabled then, let's make sure we're cleaned up + [[ "$(is_app_vhost_enabled $APP)" == "false" ]] && disable_app_vhost $APP --no-restart if [[ -z "$DOKKU_NGINX_PORT" ]]; then - if [[ -n "$NO_VHOST" ]]; then + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then dokku_log_info1 "no nginx port set. setting to random open high port" - local NGINX_PORT=$(get_open_port) + local NGINX_PORT=$(get_available_port) else local NGINX_PORT=80 fi @@ -120,7 +189,7 @@ EOF SCHEME="https" [[ -z "$NGINX_TEMPLATE" ]] && NGINX_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/nginx.ssl.conf.template" - if [[ -z "$NO_VHOST" ]]; then + if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH || true) else SSL_VHOSTS=$(< $DOKKU_ROOT/HOSTNAME) @@ -128,9 +197,9 @@ EOF NONSSL_VHOSTS=$(egrep -v "^${SSL_HOSTNAME_REGEX}$" $VHOST_PATH 2> /dev/null || true) if [[ -z "$DOKKU_NGINX_SSL_PORT" ]]; then - if [[ -n "$NO_VHOST" ]]; then + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then dokku_log_info1 "no nginx ssl port set. setting to random open high port" - local NGINX_SSL_PORT=$(get_open_port) + local NGINX_SSL_PORT=$(get_available_port) else NGINX_SSL_PORT=443 fi @@ -159,7 +228,7 @@ EOF xargs -i echo "-----> Configuring {}..." <<< "$NONSSL_VHOSTS" fi eval "cat <<< \"$(< $NGINX_TEMPLATE)\" >> $NGINX_CONF" - if [[ -n "$NO_VHOST" ]] || ([[ -z "$NONSSL_VHOSTS" ]] && [[ -z "$SSL_VHOSTS" ]]); then + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]] || ([[ -z "$NONSSL_VHOSTS" ]] && [[ -z "$SSL_VHOSTS" ]]); then sed --in-place -n -e '/^.*server_name.*$/!p' $NGINX_CONF fi @@ -184,12 +253,13 @@ EOF validate_nginx && restart_nginx fi - if [[ ! -n "$NO_VHOST" ]] && [[ -f "$DOKKU_ROOT/$APP/VHOST" ]]; then + if [[ "$(is_app_vhost_enabled $APP)" == "true" ]]; then echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > $URLS_PATH xargs -i echo "https://{}" <<< "${SSL_VHOSTS}" >> $URLS_PATH xargs -i echo "http://{}" <<< "${NONSSL_VHOSTS}" >> $URLS_PATH fi else + # note because this clause is long. if $DOKKU_NO_NGINX is set: dokku_log_info1 "nginx support is disabled for app ($APP)." if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then dokku_log_info1 "deleting nginx.conf" diff --git a/plugins/nginx-vhosts/post-deploy b/plugins/nginx-vhosts/post-deploy index cee12b518..7bdf10105 100755 --- a/plugins/nginx-vhosts/post-deploy +++ b/plugins/nginx-vhosts/post-deploy @@ -1,15 +1,12 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" APP="$1" if [[ -f "$DOKKU_ROOT/$APP/IP.web.1" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]]; then - NO_VHOST=$(config_get $APP NO_VHOST || true) - - if [[ -n "$NO_VHOST" ]]; then - dokku_log_info1 "NO_VHOST config detected" + if [[ "$(is_app_vhost_enabled $APP)" == "false" ]]; then + dokku_log_info1 "VHOST support disabled. Skipping domains setup" elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then dokku domains:setup $APP fi diff --git a/tests/unit/core_ports.bats b/tests/unit/core_ports.bats index 79f6c1b95..426de3ea8 100644 --- a/tests/unit/core_ports.bats +++ b/tests/unit/core_ports.bats @@ -142,16 +142,6 @@ check_urls() { check_urls http://www.test.app.dokku.me } -@test "(core) dockerfile port exposure" { - deploy_app dockerfile - run bash -c "grep -A1 upstream $DOKKU_ROOT/$TEST_APP/nginx.conf | grep -q 3000" - echo "output: "$output - echo "status: "$status - assert_success - - check_urls http://${TEST_APP}.dokku.me -} - @test "(core) port exposure (xip.io style hostnames)" { echo "127.0.0.1.xip.io" > "$DOKKU_ROOT/VHOST" deploy_app @@ -164,6 +154,16 @@ check_urls() { check_urls http://my-cool-guy-test-app.127.0.0.1.xip.io } +@test "(core) dockerfile port exposure" { + deploy_app dockerfile + run bash -c "grep -A1 upstream $DOKKU_ROOT/$TEST_APP/nginx.conf | grep -q 3000" + echo "output: "$output + echo "status: "$status + assert_success + + check_urls http://${TEST_APP}.dokku.me +} + @test "(core) port exposure (dockerfile raw port)" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" cat< $DOCKERFILE