Merge pull request #7577 from dokku/6872-stop-handling

Migrate DOKKU_DOCKER_STOP_TIMEOUT to ps setting
This commit is contained in:
Jose Diaz-Gonzalez
2025-06-09 13:10:42 -04:00
committed by GitHub
20 changed files with 175 additions and 36 deletions

View File

@@ -0,0 +1,6 @@
# 0.35.0 Migration Guide
## Changes
- Process stop timeouts are now configured via the `ps` property `stop-timeout-seconds`. Existing `DOKKU_DOCKER_STOP_TIMEOUT` environment variables will be automatically migrated to the new value.
- Processes now default to a `30` second stop timeout.

View File

@@ -112,7 +112,6 @@ The following config variables have special meanings and can be set in a variety
| `DOKKU_DISABLE_PROXY` | none | `dokku proxy:disable` <br /> `dokku proxy:enable` | Disables the proxy in front of your application, resulting in publicly routing the docker container. |
| `DOKKU_DISABLE_ANSI_PREFIX_REMOVAL` | none | `dokku config:set` <br /> `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | Disables removal of the ANSI prefix during deploys. Can be used in cases where the client deployer does not understand ansi escape codes. |
| `DOKKU_DISABLE_APP_AUTOCREATION` | none | `dokku config:set` | Disables automatic creation of a non-existent app on deploy. |
| `DOKKU_DOCKER_STOP_TIMEOUT` | `10` | `dokku config:set` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands _also_ respect this value. If not specified, the docker defaults for the [docker stop command](https://docs.docker.com/engine/reference/commandline/stop/) will be used.|
| `DOKKU_DOCKERFILE_CACHE_BUILD` | none | `dokku config:set` | |
| `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | |
| `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands |

View File

@@ -36,12 +36,15 @@ dokku checks:set node-js-app wait-to-retire 30
Defaults to `60`.
## Setting stop grace period
You can set the `stop-timeout-seconds` property on the `ps` plugin to change this value (default: `30`). See the [process management documentation](/docs/processes/process-management.md#changing-process-management-settings) for more information.
## Configuring check settings using the `config` plugin
There are certain settings that can be configured via environment variables:
- `DOKKU_DEFAULT_CHECKS_WAIT`: (default: `10`) If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running.
- `DOKKU_DOCKER_STOP_TIMEOUT`: (default: `10`) Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used.
The following settings may also be specified in the `app.json` file, though are available as environment variables in order to ease application reuse.

View File

@@ -107,6 +107,38 @@ proctype: qty
web: 1
```
### Changing process management settings
The `ps` plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere:
| Name | Description | Global Default |
|-----------------------|---------------------------------------------------|--------------------|
| `stop-timeout` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. | `30` |
All settings can be set via the `ps:set` command. Using `stop-timeout` as an example:
```shell
dokku ps:set node-js-app stop-timeout 60
```
The default value may be set by passing an empty value for the option in question:
```shell
dokku ps:set node-js-app stop-timeout
```
Properties can also be set globally. If not set for an app, the global value will apply.
```shell
dokku ps:set --global stop-timeout 60
```
The global default value may be set by passing an empty value for the option.
```shell
dokku ps:set --global stop-timeout
```
### Defining Processes
#### Procfile

View File

@@ -1,6 +1,6 @@
GOARCH ?= amd64
SUBCOMMANDS = subcommands/bundle subcommands/clear subcommands/export subcommands/get subcommands/keys subcommands/show subcommands/set subcommands/unset
TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/post-app-clone-setup triggers/post-app-rename-setup
TRIGGERS = triggers/config-export triggers/config-get triggers/config-get-global triggers/config-unset triggers/post-app-clone-setup triggers/post-app-rename-setup
BUILD = commands config_sub subcommands triggers
PLUGIN_NAME = config

View File

@@ -24,7 +24,7 @@ func main() {
global := flag.Arg(1)
merged := flag.Arg(2)
format := flag.Arg(3)
config.TriggerConfigExport(appName, global, merged, format)
err = config.TriggerConfigExport(appName, global, merged, format)
case "config-get":
appName := flag.Arg(0)
key := flag.Arg(1)
@@ -36,6 +36,16 @@ func main() {
case "config-get-global":
key := flag.Arg(0)
err = config.TriggerConfigGetGlobal(key)
case "config-unset":
appName := flag.Arg(0)
key := flag.Arg(1)
restart := flag.Arg(2)
if *global {
appName = "--global"
key = flag.Arg(0)
restart = flag.Arg(1)
}
err = config.TriggerConfigUnset(appName, key, common.ToBool(restart))
case "post-app-clone-setup":
oldAppName := flag.Arg(0)
newAppName := flag.Arg(1)

View File

@@ -45,6 +45,12 @@ func TriggerConfigGetGlobal(key string) error {
return nil
}
// TriggerConfigUnset unsets an app config value by key
func TriggerConfigUnset(appName string, key string, restart bool) error {
UnsetMany(appName, []string{key}, restart)
return nil
}
// TriggerPostAppCloneSetup creates new buildpacks files
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
oldEnv, err := LoadAppEnv(oldAppName)

View File

@@ -25,8 +25,16 @@ func TriggerInstall() error {
continue
}
common.LogInfo1(fmt.Sprintf("Migrating DOKKU_PROXY_PORT_MAP to ports map property for %s", appName))
portMapString := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "")
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get",
Args: []string{appName, "DOKKU_PROXY_PORT_MAP"},
})
portMapString := results.StdoutContents()
if portMapString == "" {
continue
}
common.LogVerboseQuiet(fmt.Sprintf("Setting %s ports property 'map' to %v", appName, portMapString))
portMaps, _ := parsePortMapString(portMapString)
propertyValue := []string{}
@@ -38,7 +46,11 @@ func TriggerInstall() error {
return err
}
if err := config.UnsetMany(appName, []string{"DOKKU_PROXY_PORT_MAP"}, false); err != nil {
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-unset",
Args: []string{appName, "DOKKU_PROXY_PORT_MAP"},
})
if err != nil {
return err
}
}

View File

@@ -1,5 +1,5 @@
SUBCOMMANDS = subcommands/inspect subcommands/rebuild subcommands/report subcommands/restart subcommands/restore subcommands/retire subcommands/scale subcommands/set subcommands/start subcommands/stop
TRIGGERS = triggers/app-restart triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-release-builder triggers/post-stop triggers/procfile-get-command triggers/procfile-exists triggers/ps-can-scale triggers/ps-current-scale triggers/ps-set-scale triggers/report
TRIGGERS = triggers/app-restart triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone triggers/post-app-clone-setup triggers/post-app-rename triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-release-builder triggers/post-stop triggers/procfile-get-command triggers/procfile-exists triggers/ps-can-scale triggers/ps-current-scale triggers/ps-get-property triggers/ps-set-scale triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = ps

View File

@@ -13,13 +13,15 @@ const RunInSerial = 0
var (
// DefaultProperties is a map of all valid ps properties with corresponding default property values
DefaultProperties = map[string]string{
"restart-policy": "on-failure:10",
"procfile-path": "",
"restart-policy": "on-failure:10",
"procfile-path": "",
"stop-timeout-seconds": "30",
}
// GlobalProperties is a map of all valid global ps properties
GlobalProperties = map[string]bool{
"procfile-path": true,
"procfile-path": true,
"stop-timeout-seconds": true,
}
)

View File

@@ -15,15 +15,18 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
}
flags := map[string]common.ReportFunc{
"--deployed": reportDeployed,
"--processes": reportProcesses,
"--ps-can-scale": reportCanScale,
"--ps-restart-policy": reportRestartPolicy,
"--ps-computed-procfile-path": reportComputedProcfilePath,
"--ps-global-procfile-path": reportGlobalProcfilePath,
"--ps-procfile-path": reportProcfilePath,
"--restore": reportRestore,
"--running": reportRunningState,
"--deployed": reportDeployed,
"--processes": reportProcesses,
"--ps-can-scale": reportCanScale,
"--ps-restart-policy": reportRestartPolicy,
"--ps-computed-procfile-path": reportComputedProcfilePath,
"--ps-global-procfile-path": reportGlobalProcfilePath,
"--ps-procfile-path": reportProcfilePath,
"--restore": reportRestore,
"--running": reportRunningState,
"--global-stop-timeout-seconds": reportGlobalStopTimeoutSeconds,
"--computed-stop-timeout-seconds": reportComputedStopTimeoutSeconds,
"--stop-timeout-seconds": reportStopTimeoutSeconds,
}
extraFlags := addStatusFlags(appName, infoFlag)
@@ -146,3 +149,20 @@ func reportRestore(appName string) string {
func reportRunningState(appName string) string {
return getRunningState(appName)
}
func reportComputedStopTimeoutSeconds(appName string) string {
value := reportStopTimeoutSeconds(appName)
if value == "" {
value = reportGlobalStopTimeoutSeconds(appName)
}
return value
}
func reportGlobalStopTimeoutSeconds(appName string) string {
return common.PropertyGetDefault("ps", "--global", "stop-timeout-seconds", "30")
}
func reportStopTimeoutSeconds(appName string) string {
return common.PropertyGetDefault("ps", appName, "stop-timeout-seconds", "30")
}

View File

@@ -75,8 +75,11 @@ func main() {
case "ps-current-scale":
appName := flag.Arg(0)
err = ps.TriggerPsCurrentScale(appName)
case "ps-set-scale":
case "ps-get-property":
appName := flag.Arg(0)
property := flag.Arg(1)
err = ps.TriggerPsGetProperty(appName, property)
case "ps-set-scale":
appName, args := common.ShiftString(flag.Args())
skipDeploy, args := common.ShiftString(args)
clearExisting, processTuples := common.ShiftString(args)

View File

@@ -164,8 +164,29 @@ func TriggerInstall() error {
if common.FileExists(dokkuScaleExtracted) {
os.Remove(dokkuScaleExtracted)
}
}
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get",
Args: []string{appName, "DOKKU_DOCKER_STOP_TIMEOUT"},
})
stopTimeout := results.StdoutContents()
if stopTimeout == "" {
continue
}
common.LogVerboseQuiet(fmt.Sprintf("Setting %s ps property 'stop-timeout-seconds' to %v", appName, stopTimeout))
if err := common.PropertyWrite("ps", appName, "stop-timeout-seconds", stopTimeout); err != nil {
return err
}
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-unset",
Args: []string{appName, "DOKKU_DOCKER_STOP_TIMEOUT"},
})
if err != nil {
return err
}
}
return nil
}
@@ -320,3 +341,17 @@ func TriggerPsCurrentScale(appName string) error {
func TriggerPsSetScale(appName string, skipDeploy bool, clearExisting bool, processTuples []string) error {
return scaleSet(appName, skipDeploy, clearExisting, processTuples)
}
func TriggerPsGetProperty(appName string, property string) error {
computedValueMap := map[string]common.ReportFunc{
"stop-timeout-seconds": reportComputedStopTimeoutSeconds,
}
fn, ok := computedValueMap[property]
if !ok {
return fmt.Errorf("Invalid network property specified: %v", property)
}
fmt.Println(fn(appName))
return nil
}

View File

@@ -114,7 +114,7 @@ fn-scheduler-docker-local-retire-container() {
return
fi
DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
DOKKU_DOCKER_STOP_TIMEOUT="$(plugn trigger ps-get-property "$APP" stop-timeout-seconds || true)"
[[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"
if [[ "$STATE" == "restarting" ]]; then

View File

@@ -32,7 +32,7 @@ trigger-scheduler-docker-local-scheduler-deploy() {
local oldids=$(get_app_container_ids "$APP" "$PROCESS_TYPE")
DOKKU_NETWORK_BIND_ALL="$(plugn trigger network-get-property "$APP" bind-all-interfaces)"
DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
DOKKU_DOCKER_STOP_TIMEOUT="$(plugn trigger ps-get-property "$APP" stop-timeout-seconds)"
[[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"
DOKKU_START_CMD="$(config_get "$APP" DOKKU_START_CMD || true)"

View File

@@ -51,7 +51,7 @@ trigger-scheduler-docker-local-scheduler-run-stop() {
verify_app_name "$APP"
DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
DOKKU_DOCKER_STOP_TIMEOUT="$(plugn trigger ps-get-property "$APP" stop-timeout-seconds || true)"
if [[ -n "$CONTAINER_NAME" ]]; then
fn-scheduler-docker-local-stop-container "$CONTAINER_NAME" "$DOKKU_DOCKER_STOP_TIMEOUT"
return $?

View File

@@ -14,7 +14,7 @@ trigger-scheduler-docker-local-scheduler-stop() {
fi
local DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids "$APP" 2>/dev/null)
local DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
local DOKKU_DOCKER_STOP_TIMEOUT="$(plugn trigger ps-get-property "$APP" stop-timeout-seconds || true)"
[[ -n "$DOKKU_DOCKER_STOP_TIMEOUT" ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"

View File

@@ -4,7 +4,6 @@ go 1.23.0
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.6
)
@@ -13,7 +12,6 @@ require (
github.com/fatih/color v1.18.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/joho/godotenv v1.2.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect

View File

@@ -10,8 +10,6 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc=
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
)
// TriggerSchedulerDetect outputs a manually selected scheduler for the app
@@ -36,30 +35,46 @@ func TriggerInstall() error {
return nil
}
globalScheduler := config.GetWithDefault("--global", "DOKKU_SCHEDULER", "")
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get-global",
Args: []string{"DOKKU_SCHEDULER"},
})
globalScheduler := results.StdoutContents()
if globalScheduler != "" {
common.LogVerboseQuiet(fmt.Sprintf("Setting scheduler property 'selected' to %v", globalScheduler))
if err := common.PropertyWrite("scheduler", "--global", "selected", globalScheduler); err != nil {
return err
}
if err := config.UnsetMany("--global", []string{"DOKKU_SCHEDULER"}, false); err != nil {
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-unset",
Args: []string{"--global", "DOKKU_SCHEDULER"},
})
if err != nil {
common.LogWarn(err.Error())
}
}
for _, appName := range apps {
scheduler := config.GetWithDefault(appName, "DOKKU_SCHEDULER", "")
results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-get",
Args: []string{appName, "DOKKU_SCHEDULER"},
})
scheduler := results.StdoutContents()
if scheduler == "" {
continue
}
common.LogVerboseQuiet(fmt.Sprintf("Setting scheduler property 'selected' to %v", scheduler))
common.LogVerboseQuiet(fmt.Sprintf("Setting %s scheduler property 'selected' to %v", appName, scheduler))
if err := common.PropertyWrite("scheduler", appName, "selected", scheduler); err != nil {
return err
}
if err := config.UnsetMany(appName, []string{"DOKKU_SCHEDULER"}, false); err != nil {
_, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "config-unset",
Args: []string{appName, "DOKKU_SCHEDULER"},
})
if err != nil {
common.LogWarn(err.Error())
}
}