diff --git a/aci/compose.go b/aci/compose.go
index 83c74c5f5..6fe92db0f 100644
--- a/aci/compose.go
+++ b/aci/compose.go
@@ -87,6 +87,12 @@ func (cs *aciComposeService) Copy(ctx context.Context, project *types.Project, o
 }
 
 func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
+	return progress.Run(ctx, func(ctx context.Context) error {
+		return cs.up(ctx, project)
+	})
+}
+
+func (cs *aciComposeService) up(ctx context.Context, project *types.Project) error {
 	logrus.Debugf("Up on project with name %q", project.Name)
 
 	if err := autocreateFileshares(ctx, project); err != nil {
diff --git a/api/compose/api.go b/api/compose/api.go
index 979e51771..b63c244fc 100644
--- a/api/compose/api.go
+++ b/api/compose/api.go
@@ -112,10 +112,14 @@ type CreateOptions struct {
 
 // StartOptions group options of the Start API
 type StartOptions struct {
-	// Attach will attach to service containers and send container logs and events
-	Attach ContainerEventListener
-	// Services passed in the command line to be started
-	Services []string
+	// Attach to container and forward logs if not nil
+	Attach LogConsumer
+	// AttachTo set the services to attach to
+	AttachTo []string
+	// CascadeStop stops the application when a container stops
+	CascadeStop bool
+	// ExitCodeFrom return exit code from specified service
+	ExitCodeFrom string
 }
 
 // RestartOptions group options of the Restart API
@@ -136,10 +140,8 @@ type StopOptions struct {
 
 // UpOptions group options of the Up API
 type UpOptions struct {
-	// Detach will create services and return immediately
-	Detach bool
-	// QuietPull makes the pulling process quiet
-	QuietPull bool
+	Create CreateOptions
+	Start  StartOptions
 }
 
 // DownOptions group options of the Down API
diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go
index c3bb0559d..2b724061b 100644
--- a/cli/cmd/compose/compose.go
+++ b/cli/cmd/compose/compose.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"os"
 	"os/signal"
+	"path/filepath"
 	"strings"
 	"syscall"
 
@@ -90,14 +91,43 @@ type projectOptions struct {
 // ProjectFunc does stuff within a types.Project
 type ProjectFunc func(ctx context.Context, project *types.Project) error
 
+// ProjectServicesFunc does stuff within a types.Project and a selection of services
+type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
+
 // WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
-func (o *projectOptions) WithServices(services []string, fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
-	return Adapt(func(ctx context.Context, strings []string) error {
-		project, err := o.toProject(services)
+func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
+	return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
+		return fn(ctx, project)
+	})
+}
+
+// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
+func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
+	return Adapt(func(ctx context.Context, args []string) error {
+		project, err := o.toProject(args)
 		if err != nil {
 			return err
 		}
-		return fn(ctx, project)
+
+		if o.EnvFile != "" {
+			var services types.Services
+			for _, s := range project.Services {
+				ef := o.EnvFile
+				if ef != "" {
+					if !filepath.IsAbs(ef) {
+						ef = filepath.Join(project.WorkingDir, o.EnvFile)
+					}
+					if s.Labels == nil {
+						s.Labels = make(map[string]string)
+					}
+					s.Labels[compose.EnvironmentFileLabel] = ef
+					services = append(services, s)
+				}
+			}
+			project.Services = services
+		}
+
+		return fn(ctx, project, args)
 	})
 }
 
@@ -217,7 +247,7 @@ func RootCommand(contextType string, backend compose.Service) *cobra.Command {
 	}
 
 	command.AddCommand(
-		upCommand(&opts, contextType, backend),
+		upCommand(&opts, backend),
 		downCommand(&opts, backend),
 		startCommand(&opts, backend),
 		restartCommand(&opts, backend),
diff --git a/cli/cmd/compose/create.go b/cli/cmd/compose/create.go
index 75c55124b..2b3f5a774 100644
--- a/cli/cmd/compose/create.go
+++ b/cli/cmd/compose/create.go
@@ -19,22 +19,29 @@ package compose
 import (
 	"context"
 	"fmt"
+	"time"
 
+	"github.com/compose-spec/compose-go/types"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose-cli/api/compose"
 )
 
 type createOptions struct {
-	*composeOptions
+	Build         bool
+	noBuild       bool
+	removeOrphans bool
 	forceRecreate bool
 	noRecreate    bool
+	recreateDeps  bool
+	noInherit     bool
+	timeChanged   bool
+	timeout       int
+	quietPull     bool
 }
 
 func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
-	opts := createOptions{
-		composeOptions: &composeOptions{},
-	}
+	opts := createOptions{}
 	cmd := &cobra.Command{
 		Use:   "create [SERVICE...]",
 		Short: "Creates containers for a service.",
@@ -47,17 +54,15 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 			}
 			return nil
 		}),
-		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runCreateStart(ctx, backend, upOptions{
-				composeOptions: &composeOptions{
-					projectOptions: p,
-					Build:          opts.Build,
-					noBuild:        opts.noBuild,
-				},
-				noStart:       true,
-				forceRecreate: opts.forceRecreate,
-				noRecreate:    opts.noRecreate,
-			}, args)
+		RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
+			return backend.Create(ctx, project, compose.CreateOptions{
+				RemoveOrphans:        opts.removeOrphans,
+				Recreate:             opts.recreateStrategy(),
+				RecreateDependencies: opts.dependenciesRecreateStrategy(),
+				Inherit:              !opts.noInherit,
+				Timeout:              opts.GetTimeout(),
+				QuietPull:            false,
+			})
 		}),
 	}
 	flags := cmd.Flags()
@@ -67,3 +72,46 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
 	return cmd
 }
+
+func (opts createOptions) recreateStrategy() string {
+	if opts.noRecreate {
+		return compose.RecreateNever
+	}
+	if opts.forceRecreate {
+		return compose.RecreateForce
+	}
+	return compose.RecreateDiverged
+}
+
+func (opts createOptions) dependenciesRecreateStrategy() string {
+	if opts.noRecreate {
+		return compose.RecreateNever
+	}
+	if opts.recreateDeps {
+		return compose.RecreateForce
+	}
+	return compose.RecreateDiverged
+}
+
+func (opts createOptions) GetTimeout() *time.Duration {
+	if opts.timeChanged {
+		t := time.Duration(opts.timeout) * time.Second
+		return &t
+	}
+	return nil
+}
+
+func (opts createOptions) Apply(project *types.Project) {
+	if opts.Build {
+		for i, service := range project.Services {
+			service.PullPolicy = types.PullPolicyBuild
+			project.Services[i] = service
+		}
+	}
+	if opts.noBuild {
+		for i, service := range project.Services {
+			service.Build = nil
+			project.Services[i] = service
+		}
+	}
+}
diff --git a/cli/cmd/compose/kill.go b/cli/cmd/compose/kill.go
index 9b6ab3f55..988c5da53 100644
--- a/cli/cmd/compose/kill.go
+++ b/cli/cmd/compose/kill.go
@@ -18,7 +18,6 @@ package compose
 
 import (
 	"context"
-	"os"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/spf13/cobra"
@@ -31,7 +30,7 @@ func killCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "kill [options] [SERVICE...]",
 		Short: "Force stop service containers.",
-		RunE: p.WithServices(os.Args, func(ctx context.Context, project *types.Project) error {
+		RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
 			return backend.Kill(ctx, project, opts)
 		}),
 	}
diff --git a/cli/cmd/compose/run.go b/cli/cmd/compose/run.go
index 7d619a4db..a1403d9ec 100644
--- a/cli/cmd/compose/run.go
+++ b/cli/cmd/compose/run.go
@@ -120,7 +120,11 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runRun(ctx, backend, opts)
+			project, err := p.toProject([]string{opts.Service})
+			if err != nil {
+				return err
+			}
+			return runRun(ctx, backend, project, opts)
 		}),
 	}
 	flags := cmd.Flags()
@@ -143,13 +147,8 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
 	return cmd
 }
 
-func runRun(ctx context.Context, backend compose.Service, opts runOptions) error {
-	project, err := setup(*opts.composeOptions, []string{opts.Service})
-	if err != nil {
-		return err
-	}
-
-	err = opts.apply(project)
+func runRun(ctx context.Context, backend compose.Service, project *types.Project, opts runOptions) error {
+	err := opts.apply(project)
 	if err != nil {
 		return err
 	}
diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go
index ec9e5dd3f..f4845b80d 100644
--- a/cli/cmd/compose/up.go
+++ b/cli/cmd/compose/up.go
@@ -20,40 +20,25 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"os/signal"
-	"path/filepath"
 	"strconv"
 	"strings"
-	"syscall"
-	"time"
 
 	"github.com/compose-spec/compose-go/types"
-	"github.com/docker/cli/cli"
-	"github.com/spf13/cobra"
-	"golang.org/x/sync/errgroup"
-
 	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/context/store"
-	"github.com/docker/compose-cli/api/progress"
 	"github.com/docker/compose-cli/cli/formatter"
 	"github.com/docker/compose-cli/utils"
+	"github.com/spf13/cobra"
 )
 
 // composeOptions hold options common to `up` and `run` to run compose project
 type composeOptions struct {
 	*projectOptions
-	Build   bool
-	noBuild bool
 }
 
 type upOptions struct {
 	*composeOptions
 	Detach             bool
 	Environment        []string
-	removeOrphans      bool
-	forceRecreate      bool
-	noRecreate         bool
-	recreateDeps       bool
 	noStart            bool
 	noDeps             bool
 	cascadeStop        bool
@@ -61,39 +46,7 @@ type upOptions struct {
 	scale              []string
 	noColor            bool
 	noPrefix           bool
-	timeChanged        bool
-	timeout            int
-	noInherit          bool
 	attachDependencies bool
-	quietPull          bool
-}
-
-func (opts upOptions) recreateStrategy() string {
-	if opts.noRecreate {
-		return compose.RecreateNever
-	}
-	if opts.forceRecreate {
-		return compose.RecreateForce
-	}
-	return compose.RecreateDiverged
-}
-
-func (opts upOptions) dependenciesRecreateStrategy() string {
-	if opts.noRecreate {
-		return compose.RecreateNever
-	}
-	if opts.recreateDeps {
-		return compose.RecreateForce
-	}
-	return compose.RecreateDiverged
-}
-
-func (opts upOptions) GetTimeout() *time.Duration {
-	if opts.timeChanged {
-		t := time.Duration(opts.timeout) * time.Second
-		return &t
-	}
-	return nil
 }
 
 func (opts upOptions) apply(project *types.Project, services []string) error {
@@ -136,192 +89,105 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
 	return nil
 }
 
-func upCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
-	opts := upOptions{
-		composeOptions: &composeOptions{
-			projectOptions: p,
-		},
-	}
+func upCommand(p *projectOptions, backend compose.Service) *cobra.Command {
+	up := upOptions{}
+	create := createOptions{}
 	upCmd := &cobra.Command{
 		Use:   "up [SERVICE...]",
 		Short: "Create and start containers",
 		PreRun: func(cmd *cobra.Command, args []string) {
-			opts.timeChanged = cmd.Flags().Changed("timeout")
+			create.timeChanged = cmd.Flags().Changed("timeout")
 		},
 		PreRunE: Adapt(func(ctx context.Context, args []string) error {
-			if opts.exitCodeFrom != "" {
-				opts.cascadeStop = true
+			if up.exitCodeFrom != "" {
+				up.cascadeStop = true
 			}
-			if opts.Build && opts.noBuild {
+			if create.Build && create.noBuild {
 				return fmt.Errorf("--build and --no-build are incompatible")
 			}
-			if opts.Detach && (opts.attachDependencies || opts.cascadeStop) {
+			if up.Detach && (up.attachDependencies || up.cascadeStop) {
 				return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit or --attach-dependencies")
 			}
-			if opts.forceRecreate && opts.noRecreate {
+			if create.forceRecreate && create.noRecreate {
 				return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
 			}
-			if opts.recreateDeps && opts.noRecreate {
+			if create.recreateDeps && create.noRecreate {
 				return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
 			}
 			return nil
 		}),
-		RunE: Adapt(func(ctx context.Context, args []string) error {
-			switch contextType {
-			case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
-				return runCreateStart(ctx, backend, opts, args)
-			default:
-				return runUp(ctx, backend, opts, args)
-			}
+		RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
+			return runUp(ctx, backend, create, up, project, services)
 		}),
 	}
 	flags := upCmd.Flags()
-	flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
-	flags.BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
-	flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
-	flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
-	flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
-	flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
-	flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.")
-	flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
-
-	switch contextType {
-	case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
-		flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
-		flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
-		flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.")
-		flags.BoolVar(&opts.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
-		flags.StringVar(&opts.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
-		flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
-		flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
-		flags.BoolVar(&opts.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
-		flags.BoolVarP(&opts.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
-		flags.BoolVar(&opts.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
-		flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
-	}
+	flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")
+	flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
+	flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
+	flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
+	flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
+	flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
+	flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
+	flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
+	flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
+	flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
+	flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
+	flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
+	flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
+	flags.IntVarP(&create.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
+	flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
+	flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
+	flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
+	flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
+	flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
 
 	return upCmd
 }
 
-func runUp(ctx context.Context, backend compose.Service, opts upOptions, services []string) error {
-	project, err := setup(*opts.composeOptions, services)
-	if err != nil {
-		return err
-	}
-
-	err = opts.apply(project, services)
-	if err != nil {
-		return err
-	}
-
-	return progress.Run(ctx, func(ctx context.Context) error {
-		return backend.Up(ctx, project, compose.UpOptions{
-			Detach:    opts.Detach,
-			QuietPull: opts.quietPull,
-		})
-	})
-}
-
-func runCreateStart(ctx context.Context, backend compose.Service, opts upOptions, services []string) error {
-	project, err := setup(*opts.composeOptions, services)
-	if err != nil {
-		return err
-	}
-
-	err = opts.apply(project, services)
-	if err != nil {
-		return err
-	}
-
+func runUp(ctx context.Context, backend compose.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
 	if len(project.Services) == 0 {
 		return fmt.Errorf("no service selected")
 	}
 
-	err = progress.Run(ctx, func(ctx context.Context) error {
-		err := backend.Create(ctx, project, compose.CreateOptions{
-			Services:             services,
-			RemoveOrphans:        opts.removeOrphans,
-			Recreate:             opts.recreateStrategy(),
-			RecreateDependencies: opts.dependenciesRecreateStrategy(),
-			Inherit:              !opts.noInherit,
-			Timeout:              opts.GetTimeout(),
-			QuietPull:            opts.quietPull,
-		})
-		if err != nil {
-			return err
-		}
-		if opts.Detach {
-			err = backend.Start(ctx, project, compose.StartOptions{
-				Services: services,
-			})
-		}
-		return err
-	})
+	createOptions.Apply(project)
+
+	err := upOptions.apply(project, services)
 	if err != nil {
 		return err
 	}
 
-	if opts.noStart {
-		return nil
+	var consumer compose.LogConsumer
+	if !upOptions.Detach {
+		consumer = formatter.NewLogConsumer(ctx, os.Stdout, !upOptions.noColor, !upOptions.noPrefix)
 	}
 
-	if opts.attachDependencies {
-		services = nil
+	attachTo := services
+	if upOptions.attachDependencies {
+		attachTo = project.ServiceNames()
 	}
 
-	if opts.Detach {
-		return nil
+	create := compose.CreateOptions{
+		RemoveOrphans:        createOptions.removeOrphans,
+		Recreate:             createOptions.recreateStrategy(),
+		RecreateDependencies: createOptions.dependenciesRecreateStrategy(),
+		Inherit:              !createOptions.noInherit,
+		Timeout:              createOptions.GetTimeout(),
+		QuietPull:            createOptions.quietPull,
 	}
 
-	consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix)
-	printer := compose.NewLogPrinter(consumer)
-
-	signalChan := make(chan os.Signal, 1)
-	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
-
-	stopFunc := func() error {
-		ctx := context.Background()
-		return progress.Run(ctx, func(ctx context.Context) error {
-			go func() {
-				<-signalChan
-				backend.Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
-			}()
-
-			return backend.Stop(ctx, project, compose.StopOptions{})
-		})
+	if upOptions.noStart {
+		return backend.Create(ctx, project, create)
 	}
-	go func() {
-		<-signalChan
-		printer.Cancel()
-		fmt.Println("Gracefully stopping... (press Ctrl+C again to force)")
-		stopFunc() // nolint:errcheck
-	}()
 
-	var exitCode int
-	eg, ctx := errgroup.WithContext(ctx)
-	eg.Go(func() error {
-		code, err := printer.Run(opts.cascadeStop, opts.exitCodeFrom, stopFunc)
-		exitCode = code
-		return err
+	return backend.Up(ctx, project, compose.UpOptions{
+		Create: create,
+		Start: compose.StartOptions{
+			Attach:       consumer,
+			AttachTo:     attachTo,
+			ExitCodeFrom: upOptions.exitCodeFrom,
+			CascadeStop:  upOptions.cascadeStop,
+		},
 	})
-
-	err = backend.Start(ctx, project, compose.StartOptions{
-		Attach:   printer.HandleEvent,
-		Services: services,
-	})
-	if err != nil {
-		return err
-	}
-
-	err = eg.Wait()
-	if exitCode != 0 {
-		errMsg := ""
-		if err != nil {
-			errMsg = err.Error()
-		}
-		return cli.StatusError{StatusCode: exitCode, Status: errMsg}
-	}
-	return err
 }
 
 func setServiceScale(project *types.Project, name string, replicas int) error {
@@ -338,43 +204,3 @@ func setServiceScale(project *types.Project, name string, replicas int) error {
 	}
 	return fmt.Errorf("unknown service %q", name)
 }
-
-func setup(opts composeOptions, services []string) (*types.Project, error) {
-	project, err := opts.toProject(services)
-	if err != nil {
-		return nil, err
-	}
-
-	if opts.Build {
-		for i, service := range project.Services {
-			service.PullPolicy = types.PullPolicyBuild
-			project.Services[i] = service
-		}
-	}
-	if opts.noBuild {
-		for i, service := range project.Services {
-			service.Build = nil
-			project.Services[i] = service
-		}
-	}
-
-	if opts.EnvFile != "" {
-		var services types.Services
-		for _, s := range project.Services {
-			ef := opts.EnvFile
-			if ef != "" {
-				if !filepath.IsAbs(ef) {
-					ef = filepath.Join(project.WorkingDir, opts.EnvFile)
-				}
-				if s.Labels == nil {
-					s.Labels = make(map[string]string)
-				}
-				s.Labels[compose.EnvironmentFileLabel] = ef
-				services = append(services, s)
-			}
-		}
-		project.Services = services
-	}
-
-	return project, nil
-}
diff --git a/ecs/up.go b/ecs/up.go
index 691a52c23..3829e680d 100644
--- a/ecs/up.go
+++ b/ecs/up.go
@@ -23,12 +23,12 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/compose-spec/compose-go/types"
 	"github.com/sirupsen/logrus"
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/errdefs"
-
-	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose-cli/api/progress"
 )
 
 func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error {
@@ -80,6 +80,12 @@ func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, option
 }
 
 func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
+	return progress.Run(ctx, func(ctx context.Context) error {
+		return b.up(ctx, project, options)
+	})
+}
+
+func (b *ecsAPIService) up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
 	logrus.Debugf("deploying on AWS with region=%q", b.Region)
 	err := b.aws.CheckRequirements(ctx, b.Region)
 	if err != nil {
@@ -124,7 +130,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options
 			return err
 		}
 	}
-	if options.Detach {
+	if options.Start.Attach == nil {
 		return nil
 	}
 	signalChan := make(chan os.Signal, 1)
diff --git a/kube/compose.go b/kube/compose.go
index 7b4e91330..b89ac0560 100644
--- a/kube/compose.go
+++ b/kube/compose.go
@@ -72,6 +72,12 @@ func NewComposeService() (compose.Service, error) {
 
 // Up executes the equivalent to a `compose up`
 func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
+	return progress.Run(ctx, func(ctx context.Context) error {
+		return s.up(ctx, project)
+	})
+}
+
+func (s *composeService) up(ctx context.Context, project *types.Project) error {
 	w := progress.ContextWriter(ctx)
 
 	eventName := "Convert Compose file to Helm charts"
diff --git a/local/compose/compose.go b/local/compose/compose.go
index 891b1d95c..f2567801f 100644
--- a/local/compose/compose.go
+++ b/local/compose/compose.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 
 	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/errdefs"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/cli/config/configfile"
@@ -45,10 +44,6 @@ type composeService struct {
 	configFile *configfile.ConfigFile
 }
 
-func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
-	return errdefs.ErrNotImplemented
-}
-
 func getCanonicalContainerName(c moby.Container) string {
 	// Names return container canonical name /foo  + link aliases /linked_by/foo
 	for _, name := range c.Names {
diff --git a/local/compose/create.go b/local/compose/create.go
index ccf92818e..d1c79eef2 100644
--- a/local/compose/create.go
+++ b/local/compose/create.go
@@ -43,9 +43,15 @@ import (
 	"github.com/docker/compose-cli/utils"
 )
 
-func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
-	if len(opts.Services) == 0 {
-		opts.Services = project.ServiceNames()
+func (s *composeService) Create(ctx context.Context, project *types.Project, options compose.CreateOptions) error {
+	return progress.Run(ctx, func(ctx context.Context) error {
+		return s.create(ctx, project, options)
+	})
+}
+
+func (s *composeService) create(ctx context.Context, project *types.Project, options compose.CreateOptions) error {
+	if len(options.Services) == 0 {
+		options.Services = project.ServiceNames()
 	}
 
 	var observedState Containers
@@ -56,7 +62,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
 	containerState := NewContainersState(observedState)
 	ctx = context.WithValue(ctx, ContainersKey{}, containerState)
 
-	err = s.ensureImagesExists(ctx, project, observedState, opts.QuietPull)
+	err = s.ensureImagesExists(ctx, project, observedState, options.QuietPull)
 	if err != nil {
 		return err
 	}
@@ -83,7 +89,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
 	}
 	orphans := observedState.filter(isNotService(allServiceNames...))
 	if len(orphans) > 0 {
-		if opts.RemoveOrphans {
+		if options.RemoveOrphans {
 			w := progress.ContextWriter(ctx)
 			err := s.removeContainers(ctx, w, orphans, nil)
 			if err != nil {
@@ -100,10 +106,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
 	prepareServicesDependsOn(project)
 
 	return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
-		if utils.StringContains(opts.Services, service.Name) {
-			return s.ensureService(c, project, service, opts.Recreate, opts.Inherit, opts.Timeout)
+		if utils.StringContains(options.Services, service.Name) {
+			return s.ensureService(c, project, service, options.Recreate, options.Inherit, options.Timeout)
 		}
-		return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit, opts.Timeout)
+		return s.ensureService(c, project, service, options.RecreateDependencies, options.Inherit, options.Timeout)
 	})
 }
 
diff --git a/local/compose/kill.go b/local/compose/kill.go
index 0ec788e89..4b951a5a4 100644
--- a/local/compose/kill.go
+++ b/local/compose/kill.go
@@ -28,6 +28,12 @@ import (
 )
 
 func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error {
+	return progress.Run(ctx, func(ctx context.Context) error {
+		return s.kill(ctx, project, options)
+	})
+}
+
+func (s *composeService) kill(ctx context.Context, project *types.Project, options compose.KillOptions) error {
 	w := progress.ContextWriter(ctx)
 
 	var containers Containers
diff --git a/local/compose/kill_test.go b/local/compose/kill_test.go
index 58b2ec97d..6216a67d8 100644
--- a/local/compose/kill_test.go
+++ b/local/compose/kill_test.go
@@ -50,7 +50,7 @@ func TestKillAll(t *testing.T) {
 	api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
 	api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
 
-	err := tested.Kill(ctx, &project, compose.KillOptions{})
+	err := tested.kill(ctx, &project, compose.KillOptions{})
 	assert.NilError(t, err)
 }
 
@@ -66,7 +66,7 @@ func TestKillSignal(t *testing.T) {
 	api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]apitypes.Container{testContainer("service1", "123")}, nil)
 	api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
 
-	err := tested.Kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
+	err := tested.kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
 	assert.NilError(t, err)
 }
 
diff --git a/local/compose/start.go b/local/compose/start.go
index 724aeeef2..a5c513072 100644
--- a/local/compose/start.go
+++ b/local/compose/start.go
@@ -19,45 +19,40 @@ package compose
 import (
 	"context"
 
-	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/progress"
-	"github.com/docker/compose-cli/utils"
-
 	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
+
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/progress"
 )
 
 func (s *composeService) Start(ctx context.Context, project *types.Project, options compose.StartOptions) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
-		return s.start(ctx, project, options)
+		return s.start(ctx, project, options, nil)
 	})
 }
 
-func (s *composeService) start(ctx context.Context, project *types.Project, options compose.StartOptions) error {
-	listener := options.Attach
-	if len(options.Services) == 0 {
-		options.Services = project.ServiceNames()
+func (s *composeService) start(ctx context.Context, project *types.Project, options compose.StartOptions, listener func(event compose.ContainerEvent)) error {
+	if len(options.AttachTo) == 0 {
+		options.AttachTo = project.ServiceNames()
 	}
 
 	eg, ctx := errgroup.WithContext(ctx)
 	if listener != nil {
-		attached, err := s.attach(ctx, project, listener, options.Services)
+		attached, err := s.attach(ctx, project, listener, options.AttachTo)
 		if err != nil {
 			return err
 		}
 
 		eg.Go(func() error {
-			return s.watchContainers(project, options.Services, listener, attached)
+			return s.watchContainers(project, options.AttachTo, listener, attached)
 		})
 	}
 
 	err := InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
-		if utils.StringContains(options.Services, service.Name) {
-			return s.startService(ctx, project, service)
-		}
-		return nil
+		return s.startService(ctx, project, service)
 	})
 	if err != nil {
 		return err
diff --git a/local/compose/up.go b/local/compose/up.go
new file mode 100644
index 000000000..72a30165b
--- /dev/null
+++ b/local/compose/up.go
@@ -0,0 +1,95 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/progress"
+
+	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli"
+	"golang.org/x/sync/errgroup"
+)
+
+func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
+	err := progress.Run(ctx, func(ctx context.Context) error {
+		err := s.create(ctx, project, options.Create)
+		if err != nil {
+			return err
+		}
+		return s.start(ctx, project, options.Start, nil)
+	})
+	if err != nil {
+		return err
+	}
+
+	if options.Start.Attach == nil {
+		return err
+	}
+
+	printer := compose.NewLogPrinter(options.Start.Attach)
+
+	signalChan := make(chan os.Signal, 1)
+	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
+
+	stopFunc := func() error {
+		ctx := context.Background()
+		return progress.Run(ctx, func(ctx context.Context) error {
+			go func() {
+				<-signalChan
+				s.Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
+			}()
+
+			return s.Stop(ctx, project, compose.StopOptions{})
+		})
+	}
+	go func() {
+		<-signalChan
+		printer.Cancel()
+		fmt.Println("Gracefully stopping... (press Ctrl+C again to force)")
+		stopFunc() // nolint:errcheck
+	}()
+
+	var exitCode int
+	eg, ctx := errgroup.WithContext(ctx)
+	eg.Go(func() error {
+		code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
+		exitCode = code
+		return err
+	})
+
+	err = s.start(ctx, project, options.Start, printer.HandleEvent)
+	if err != nil {
+		return err
+	}
+
+	err = eg.Wait()
+	if exitCode != 0 {
+		errMsg := ""
+		if err != nil {
+			errMsg = err.Error()
+		}
+		return cli.StatusError{StatusCode: exitCode, Status: errMsg}
+	}
+	return err
+}