feat: add custom health check commands per app
All checks were successful
Check / check (pull_request) Successful in 1m53s

Add configurable health check commands per app via a new
'healthcheck_command' field. When set, the command is passed
to Docker as a CMD-SHELL health check on the container.
When empty, the image's default health check is used.

Changes:
- Add migration 007 for healthcheck_command column on apps table
- Add HealthcheckCommand field to App model with full CRUD support
- Add buildHealthcheck() to docker client for CMD-SHELL config
- Pass health check command through CreateContainerOptions
- Add health check command input to app create/edit UI forms
- Extract optionalNullString helper to reduce handler complexity
- Update README features list

closes #81
This commit is contained in:
user
2026-03-17 02:11:08 -07:00
parent fd110e69db
commit e2522f2017
12 changed files with 342 additions and 92 deletions

View File

@@ -13,6 +13,7 @@ import (
"regexp"
"strconv"
"strings"
"time"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@@ -138,13 +139,14 @@ func (c *Client) BuildImage(
// CreateContainerOptions contains options for creating a container.
type CreateContainerOptions struct {
Name string
Image string
Env map[string]string
Labels map[string]string
Volumes []VolumeMount
Ports []PortMapping
Network string
Name string
Image string
Env map[string]string
Labels map[string]string
Volumes []VolumeMount
Ports []PortMapping
Network string
HealthcheckCommand string // Custom health check shell command (empty = use image default)
}
// VolumeMount represents a volume mount.
@@ -185,6 +187,29 @@ func buildPortConfig(ports []PortMapping) (nat.PortSet, nat.PortMap) {
return exposedPorts, portBindings
}
// healthcheckInterval is the time between health check attempts.
const healthcheckInterval = 30 * time.Second
// healthcheckTimeout is the maximum time a single health check can take.
const healthcheckTimeout = 10 * time.Second
// healthcheckStartPeriod is the grace period before health checks start counting failures.
const healthcheckStartPeriod = 15 * time.Second
// healthcheckRetries is the number of consecutive failures needed to mark unhealthy.
const healthcheckRetries = 3
// buildHealthcheck creates a Docker health check config from a shell command string.
func buildHealthcheck(command string) *container.HealthConfig {
return &container.HealthConfig{
Test: []string{"CMD-SHELL", command},
Interval: healthcheckInterval,
Timeout: healthcheckTimeout,
StartPeriod: healthcheckStartPeriod,
Retries: healthcheckRetries,
}
}
// CreateContainer creates a new container.
func (c *Client) CreateContainer(
ctx context.Context,
@@ -218,14 +243,22 @@ func (c *Client) CreateContainer(
// Convert ports to exposed ports and port bindings
exposedPorts, portBindings := buildPortConfig(opts.Ports)
// Build container config
containerConfig := &container.Config{
Image: opts.Image,
Env: envSlice,
Labels: opts.Labels,
ExposedPorts: exposedPorts,
}
// Apply custom health check if configured
if opts.HealthcheckCommand != "" {
containerConfig.Healthcheck = buildHealthcheck(opts.HealthcheckCommand)
}
// Create container
resp, err := c.docker.ContainerCreate(ctx,
&container.Config{
Image: opts.Image,
Env: envSlice,
Labels: opts.Labels,
ExposedPorts: exposedPorts,
},
containerConfig,
&container.HostConfig{
Mounts: mounts,
PortBindings: portBindings,