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

  1. 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...]'
  2. >=8080
  3. '80'
  4. '53/udp'.
  5. 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.
  6. Remove host-IP or range syntax that is unsupported and use the simplest 'host:container' form.
  7. 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

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

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/ba3c37ef0893d7b5. Report an issue: GitHub.