{"record":{"id":"ba3c37ef0893d7b5","repo":"nektos/act","slug":"failed-to-parse-service-s-ports-w","errorCode":null,"errorMessage":"failed to parse service %s ports: %w","messagePattern":"failed to parse service (.+?) ports: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/runner/run_context.go","lineNumber":321,"sourceCode":"\t\t\t}\n\t\t\tusername, password, err = rc.handleServiceCredentials(ctx, spec.Credentials)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to handle service %s credentials: %w\", serviceID, err)\n\t\t\t}\n\n\t\t\tinterpolatedVolumes := make([]string, 0, len(spec.Volumes))\n\t\t\tfor _, volume := range spec.Volumes {\n\t\t\t\tinterpolatedVolumes = append(interpolatedVolumes, rc.ExprEval.Interpolate(ctx, volume))\n\t\t\t}\n\t\t\tserviceBinds, serviceMounts := rc.GetServiceBindsAndMounts(interpolatedVolumes)\n\n\t\t\tinterpolatedPorts := make([]string, 0, len(spec.Ports))\n\t\t\tfor _, port := range spec.Ports {\n\t\t\t\tinterpolatedPorts = append(interpolatedPorts, rc.ExprEval.Interpolate(ctx, port))\n\t\t\t}\n\t\t\texposedPorts, portBindings, err := nat.ParsePortSpecs(interpolatedPorts)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse service %s ports: %w\", serviceID, err)\n\t\t\t}\n\n\t\t\timageName := rc.ExprEval.Interpolate(ctx, spec.Image)\n\t\t\tif imageName == \"\" {\n\t\t\t\tlogger.Infof(\"The service '%s' will not be started because the container definition has an empty image.\", serviceID)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tserviceContainerName := createContainerName(rc.jobContainerName(), serviceID)\n\t\t\tc := container.NewContainer(&container.NewContainerInput{\n\t\t\t\tName:           serviceContainerName,\n\t\t\t\tWorkingDir:     ext.ToContainerPath(rc.Config.Workdir),\n\t\t\t\tImage:          imageName,\n\t\t\t\tUsername:       username,\n\t\t\t\tPassword:       password,\n\t\t\t\tEnv:            envs,\n\t\t\t\tMounts:         serviceMounts,\n\t\t\t\tBinds:          serviceBinds,","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/run_context.go#L303-L339","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","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."],"exampleFix":"# before\nservices:\n  redis:\n    image: redis\n    ports:\n      - '${{ matrix.port }}'   # matrix.port undefined -> ''\n# after\nservices:\n  redis:\n    image: redis\n    ports:\n      - '6379:6379'","handlingStrategy":"validation","validationCode":"// Validate service port specs before running act (Go, mirroring Docker nat rules)\nimport (\n  \"fmt\"\n  \"regexp\"\n)\nvar portRe = regexp.MustCompile(`^([0-9.]+:)?([0-9]+|[0-9]+-[0-9]+)(:[0-9]+(-[0-9]+)?)?(/tcp|/udp|/sctp)?$`)\nfunc validPorts(ports []string) error {\n  for _, p := range ports {\n    if !portRe.MatchString(p) {\n      return fmt.Errorf(\"bad port spec %q\", p)\n    }\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["docker","services","ports","workflow-syntax","configuration"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}