nektos/act · error
failed to parse service %s ports: %w
Error message
failed to parse service %s ports: %w
What it means
Thrown when Docker's nat.ParsePortSpecs fails to parse the interpolated `ports:` entries of a job's service container definition. Each port string (after ${{ }} interpolation) must match Docker's port-spec grammar (e.g. '8080:80', '127.0.0.1:8080:80/tcp'); any malformed entry makes ParsePortSpecs return an error, which act wraps with the service name.
Source
Thrown at pkg/runner/run_context.go:321
}
username, password, err = rc.handleServiceCredentials(ctx, spec.Credentials)
if err != nil {
return fmt.Errorf("failed to handle service %s credentials: %w", serviceID, err)
}
interpolatedVolumes := make([]string, 0, len(spec.Volumes))
for _, volume := range spec.Volumes {
interpolatedVolumes = append(interpolatedVolumes, rc.ExprEval.Interpolate(ctx, volume))
}
serviceBinds, serviceMounts := rc.GetServiceBindsAndMounts(interpolatedVolumes)
interpolatedPorts := make([]string, 0, len(spec.Ports))
for _, port := range spec.Ports {
interpolatedPorts = append(interpolatedPorts, rc.ExprEval.Interpolate(ctx, port))
}
exposedPorts, portBindings, err := nat.ParsePortSpecs(interpolatedPorts)
if err != nil {
return fmt.Errorf("failed to parse service %s ports: %w", serviceID, err)
}
imageName := rc.ExprEval.Interpolate(ctx, spec.Image)
if imageName == "" {
logger.Infof("The service '%s' will not be started because the container definition has an empty image.", serviceID)
continue
}
serviceContainerName := createContainerName(rc.jobContainerName(), serviceID)
c := container.NewContainer(&container.NewContainerInput{
Name: serviceContainerName,
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
Image: imageName,
Username: username,
Password: password,
Env: envs,
Mounts: serviceMounts,
Binds: serviceBinds,View on GitHub (pinned to 4f41128141)
Solutions
- Check the exact ports list printed for the failing service and fix or remove malformed entries; every entry must be '[hostIP:]hostPort[:containerPort][/proto|/range...]'
- >=8080
- '80'
- '53/udp'.
- If the port comes from an expression like ${{ matrix.port }}, verify the matrix key exists and the value is numeric — an empty result after interpolation fails parsing.
- Remove host-IP or range syntax that is unsupported and use the simplest 'host:container' form.
- Lint the workflow with actionlint or `act --list` to catch syntax problems before execution.
Example fix
# before
services:
redis:
image: redis
ports:
- '${{ matrix.port }}' # matrix.port undefined -> ''
# after
services:
redis:
image: redis
ports:
- '6379:6379' Defensive patterns
Strategy: validation
Validate before calling
// Validate service port specs before running act (Go, mirroring Docker nat rules)
import (
"fmt"
"regexp"
)
var portRe = regexp.MustCompile(`^([0-9.]+:)?([0-9]+|[0-9]+-[0-9]+)(:[0-9]+(-[0-9]+)?)?(/tcp|/udp|/sctp)?$`)
func validPorts(ports []string) error {
for _, p := range ports {
if !portRe.MatchString(p) {
return fmt.Errorf("bad port spec %q", p)
}
}
return nil
} Prevention
- Keep every ports entry in simple 'host:container' form.
- actionlint the workflow before act runs.
- Never let matrix-driven ports interpolate to empty: give matrix values defaults.
When it happens
Trigger: A `services.<id>.ports:` list entry in the job definition that is empty after interpolation (e.g. `${{ matrix.port }}` resolves to ''), has an invalid protocol suffix, a non-numeric port, malformed host:container:range syntax, or unparseable ranges like '80-:80'.
Common situations: Matrix-driven port lists where the matrix key is misspelled so interpolation yields an empty string; copying docker-compose range syntax ('8080-8090:80-90' is valid but typos like '8080:' are not); using UDP with ranges incorrectly; passing 'tcp://' style URLs instead of '/tcp' suffix.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- --pid: invalid PID mode
- network-scoped aliases are only supported for user-defined n
- links are only supported for user-defined networks
- invalid range format for --expose: %w
- invalid publish opts format (should be name=value but got '%
AI-assisted analysis of nektos/act@4f41128141 (2026-08-15).
Data as JSON: /api/errors/ba3c37ef0893d7b5.
Report an issue: GitHub.