docker/compose · error

--service-ports and --publish are incompatible

Error message

--service-ports and --publish are incompatible

What it means

`docker compose run` refuses combining `--service-ports` (publish all ports from the service definition) with explicit `--publish/-p` overrides; the PreRunE check fires as soon as any publish value exists alongside servicePorts.

Source

Thrown at cmd/compose/run.go:171

	createOpts := createOptions{}
	buildOpts := buildOptions{
		ProjectOptions: p,
	}
	// We remove the attribute from the option struct and use a dedicated var, to limit confusion and avoid anyone to use options.tty.
	// The tty flag is here for convenience and let user do "docker compose run -it" the same way as they use the "docker run" command.
	var ttyFlag bool

	cmd := &cobra.Command{
		Use:   "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
		Short: "Run a one-off command on a service",
		Args:  cobra.MinimumNArgs(1),
		PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
			options.Service = args[0]
			if len(args) > 1 {
				options.Command = args[1:]
			}
			if len(options.publish) > 0 && options.servicePorts {
				return fmt.Errorf("--service-ports and --publish are incompatible")
			}
			if cmd.Flags().Changed("entrypoint") {
				command, err := shellwords.Parse(options.entrypoint)
				if err != nil {
					return err
				}
				options.entrypointCmd = command
			}
			if cmd.Flags().Changed("tty") {
				if cmd.Flags().Changed("no-tty") {
					return fmt.Errorf("--tty and --no-tty can't be used together")
				} else {
					options.noTty = !ttyFlag
				}
			} else if !cmd.Flags().Changed("no-tty") && !cmd.Flags().Changed("interactive") && !dockerCli.In().IsTerminal() {
				// while `docker run` requires explicit `-it` flags, Compose enables interactive mode and TTY by default
				// but when compose is used from a script that has stdin piped from another command, we just can't
				// Here, we detect we run "by default" (user didn't passed explicit flags) and disable TTY allocation if

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Drop `--service-ports` and keep only the explicit `--publish` mappings you need.
  2. Or drop the `--publish` flags and rely on the service's declared ports via `--service-ports`.
  3. Re-express the desired mapping in the service definition or an override file if you need both sets.

Example fix

# before
docker compose run --service-ports -p 8080:80 web

# after
docker compose run -p 8080:80 web
Defensive patterns

Strategy: validation

Validate before calling

# bash: exclusive port strategy
if [ ${#PUBLISH[@]} -gt 0 ] && [ "$SERVICE_PORTS" = true ]; then
  echo "pick --service-ports OR --publish, not both" >&2; exit 2
fi

Prevention

When it happens

Trigger: Running `docker compose run --service-ports -p 8080:80 web` — any combination where options.publish is non-empty and options.servicePorts is true.

Common situations: Adding a temporary port override for a one-off run without dropping the habitual --service-ports flag; CI one-off exec commands that stack both flags 'for completeness'; shell aliases including --service-ports.

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/28fe7e3f3bfeb7d7. Report an issue: GitHub.