Repository: docker/compose-cli
Pull request #1400 (2021-03-05 13:58:22)

Add connection flags to root command
**What I did**
Added common flags to the root command: tls, cert flags etc.

Initialized the API client with the docker/cli `NewAPIClientFromFlags` which processes the config and tls options. This way we avoid duplicating the code that is already in the original cli. 


Set up a secured Docker engine following https://docs.docker.com/engine/security/protect-access/#use-tls-https-to-protect-the-docker-daemon-socket

```
$ docker -H=127.0.0.1:2376 compose ps
The new 'docker compose' command is currently experimental. To provide feedback or request new features please open issues at https://github.com/docker/compose-cli
Error response from daemon: Client sent an HTTP request to an HTTPS server.

```
```
$ docker --tlsverify --tlscacert=/tmp/certs/ca.pem --tlscert=/tmp/certs/cert.pem --tlskey=/tmp/certs/key.pem -H=127.0.0.1:2376 compose ps
NAME                SERVICE             STATUS              PORTS
```

```
$ docker --tlsverify --tlscacert=/tmp/certs/ca.pem --tlscert=/tmp/certs/cert.pem --tlskey=/tmp/certs/key.pem -H=127.0.0.1:2376 compose up -d
[+] Running 1/1
 ⠿ Container compose-cli_web_1  Started                                                                                                                                      0.8s
```
```
$ docker --tlsverify --tlscacert=/tmp/certs/ca.pem --tlscert=/tmp/certs/cert.pem --tlskey=/tmp/certs/key.pem -H=127.0.0.1:2376 compose ps
NAME                SERVICE             STATUS              PORTS
compose-cli_web_1   web                 running             0.0.0.0:8080->80/tcp
```
```
$ docker --tlsverify --tlscacert=/tmp/certs/ca.pem --tlscert=/tmp/certs/cert.pem --tlskey=/tmp/certs/key.pem -H=127.0.0.1:2376 compose down
[+] Running 2/2
 ⠿ Container compose-cli_web_1    Removed                                                                                                                                    0.5s
 ⠿ Network "compose-cli_default"  Removed                                                                                                                                    0.5s


```




```
$ docker context use sshcontext
sshcontext

$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT                 ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   https://127.0.0.1:42745 (default)   swarm
sshcontext *        moby                My HOME server                            ssh://host                                                         
```

```
$ docker compose up
[+] Running 2/1
 ⠿ Network "compose-cli_default"  Created       0.3s
 ⠿ Container compose-cli_web_1    Created       0.0s
Attaching to compose-cli_web_1
...
```

```
$ docker -H ssh://host compose ps
NAME                SERVICE             STATUS              PORTS
compose-cli_web_1   web                 running             0.0.0.0:8080->80/tcp
```
```
$ docker compose ps
NAME                SERVICE             STATUS              PORTS
compose-cli_web_1   web                 running             0.0.0.0:8080->80/tcp

```
 

/test-kube
/test-aci
/test-ecs
/test-windows


Patch:
diff --git a/aci/compose.go b/aci/compose.go
index 783e7469..183b2080 100644
--- a/aci/compose.go
+++ b/aci/compose.go
@@ -226,6 +226,3 @@ func (cs *aciComposeService) Remove(ctx context.Context, project *types.Project,
 func (cs *aciComposeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
 	return errdefs.ErrNotImplemented
 }
-func (cs *aciComposeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	return nil, errdefs.ErrNotImplemented
-}
diff --git a/api/client/compose.go b/api/client/compose.go
index 90a25572..1e8b2398 100644
--- a/api/client/compose.go
+++ b/api/client/compose.go
@@ -99,7 +99,3 @@ func (c *composeService) Pause(ctx context.Context, project *types.Project) erro
 func (c *composeService) UnPause(ctx context.Context, project *types.Project) error {
 	return errdefs.ErrNotImplemented
 }
-
-func (c *composeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	return nil, errdefs.ErrNotImplemented
-}
diff --git a/api/compose/api.go b/api/compose/api.go
index ecac82c8..9803d9da 100644
--- a/api/compose/api.go
+++ b/api/compose/api.go
@@ -63,8 +63,6 @@ type Service interface {
 	Pause(ctx context.Context, project *types.Project) error
 	// UnPause executes the equivalent to a `compose unpause`
 	UnPause(ctx context.Context, project *types.Project) error
-	// Top executes the equivalent to a `compose top`
-	Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
 }
 
 // BuildOptions group options of the Build API
@@ -158,21 +156,20 @@ type RemoveOptions struct {
 
 // RunOptions options to execute compose run
 type RunOptions struct {
-	Name              string
-	Service           string
-	Command           []string
-	Entrypoint        []string
-	Detach            bool
-	AutoRemove        bool
-	Writer            io.Writer
-	Reader            io.Reader
-	Tty               bool
-	WorkingDir        string
-	User              string
-	Environment       []string
-	Labels            types.Labels
-	Privileged        bool
-	UseNetworkAliases bool
+	Name        string
+	Service     string
+	Command     []string
+	Entrypoint  []string
+	Detach      bool
+	AutoRemove  bool
+	Writer      io.Writer
+	Reader      io.Reader
+	Tty         bool
+	WorkingDir  string
+	User        string
+	Environment []string
+	Labels      types.Labels
+	Privileged  bool
 	// used by exec
 	Index int
 }
@@ -222,14 +219,6 @@ type ContainerSummary struct {
 	Publishers []PortPublisher
 }
 
-// ContainerProcSummary holds container processes top data
-type ContainerProcSummary struct {
-	ID        string
-	Name      string
-	Processes [][]string
-	Titles    []string
-}
-
 // ServiceStatus hold status about a service
 type ServiceStatus struct {
 	ID         string
diff --git a/api/context/context.go b/api/context/context.go
index b1138c31..27ac5e36 100644
--- a/api/context/context.go
+++ b/api/context/context.go
@@ -20,9 +20,12 @@ import (
 	gocontext "context"
 
 	"golang.org/x/net/context"
+
+	cliflags "github.com/docker/cli/cli/flags"
 )
 
 type currentContextKey struct{}
+type cliOptionsKey struct{}
 
 // WithCurrentContext sets the name of the current docker context
 func WithCurrentContext(ctx gocontext.Context, contextName string) context.Context {
@@ -34,3 +37,14 @@ func CurrentContext(ctx context.Context) string {
 	cc, _ := ctx.Value(currentContextKey{}).(string)
 	return cc
 }
+
+// WithCliOptions sets CLI options
+func WithCliOptions(ctx gocontext.Context, options cliflags.CommonOptions) context.Context {
+	return context.WithValue(ctx, cliOptionsKey{}, options)
+}
+
+// CliOptions returns common cli options
+func CliOptions(ctx context.Context) cliflags.CommonOptions {
+	cc, _ := ctx.Value(cliOptionsKey{}).(cliflags.CommonOptions)
+	return cc
+}
diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go
index b3cfb420..7f01f8c5 100644
--- a/cli/cmd/compose/compose.go
+++ b/cli/cmd/compose/compose.go
@@ -135,7 +135,6 @@ func Command(contextType string) *cobra.Command {
 		execCommand(&opts),
 		pauseCommand(&opts),
 		unpauseCommand(&opts),
-		topCommand(&opts),
 	)
 
 	if contextType == store.LocalContextType || contextType == store.DefaultContextType {
diff --git a/cli/cmd/compose/run.go b/cli/cmd/compose/run.go
index 8729316f..4eacd070 100644
--- a/cli/cmd/compose/run.go
+++ b/cli/cmd/compose/run.go
@@ -22,7 +22,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/mattn/go-shellwords"
 	"github.com/spf13/cobra"
@@ -35,69 +34,18 @@ import (
 
 type runOptions struct {
 	*composeOptions
-	Service      string
-	Command      []string
-	environment  []string
-	Detach       bool
-	Remove       bool
-	noTty        bool
-	user         string
-	workdir      string
-	entrypoint   string
-	labels       []string
-	volumes      []string
-	publish      []string
-	useAliases   bool
-	servicePorts bool
-	name         string
-	noDeps       bool
-}
-
-func (opts runOptions) apply(project *types.Project) error {
-	target, err := project.GetService(opts.Service)
-	if err != nil {
-		return err
-	}
-	if !opts.servicePorts {
-		target.Ports = []types.ServicePortConfig{}
-	}
-	if len(opts.publish) > 0 {
-		target.Ports = []types.ServicePortConfig{}
-		for _, p := range opts.publish {
-			config, err := types.ParsePortConfig(p)
-			if err != nil {
-				return err
-			}
-			target.Ports = append(target.Ports, config...)
-		}
-	}
-	if len(opts.volumes) > 0 {
-		target.Volumes = []types.ServiceVolumeConfig{}
-		for _, v := range opts.volumes {
-			volume, err := loader.ParseVolume(v)
-			if err != nil {
-				return err
-			}
-			target.Volumes = append(target.Volumes, volume)
-		}
-	}
-
-	if opts.noDeps {
-		for _, s := range project.Services {
-			if s.Name != opts.Service {
-				project.DisabledServices = append(project.DisabledServices, s)
-			}
-		}
-		project.Services = types.Services{target}
-	}
-
-	for i, s := range project.Services {
-		if s.Name == opts.Service {
-			project.Services[i] = target
-			break
-		}
-	}
-	return nil
+	Service     string
+	Command     []string
+	environment []string
+	Detach      bool
+	Remove      bool
+	noTty       bool
+	user        string
+	workdir     string
+	entrypoint  string
+	labels      []string
+	name        string
+	noDeps      bool
 }
 
 func runCommand(p *projectOptions) *cobra.Command {
@@ -115,9 +63,6 @@ func runCommand(p *projectOptions) *cobra.Command {
 				opts.Command = args[1:]
 			}
 			opts.Service = args[0]
-			if len(opts.publish) > 0 && opts.servicePorts {
-				return fmt.Errorf("--service-ports and --publish are incompatible")
-			}
 			return runRun(cmd.Context(), opts)
 		},
 	}
@@ -132,10 +77,6 @@ func runCommand(p *projectOptions) *cobra.Command {
 	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
 	flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
 	flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
-	flags.StringArrayVarP(&opts.volumes, "volumes", "v", []string{}, "Bind mount a volume.")
-	flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
-	flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
-	flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
 
 	flags.SetInterspersed(false)
 	return cmd
@@ -147,9 +88,17 @@ func runRun(ctx context.Context, opts runOptions) error {
 		return err
 	}
 
-	err = opts.apply(project)
-	if err != nil {
-		return err
+	if opts.noDeps {
+		enabled, err := project.GetService(opts.Service)
+		if err != nil {
+			return err
+		}
+		for _, s := range project.Services {
+			if s.Name != opts.Service {
+				project.DisabledServices = append(project.DisabledServices, s)
+			}
+		}
+		project.Services = types.Services{enabled}
 	}
 
 	_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
@@ -178,21 +127,20 @@ func runRun(ctx context.Context, opts runOptions) error {
 
 	// start container and attach to container streams
 	runOpts := compose.RunOptions{
-		Name:              opts.name,
-		Service:           opts.Service,
-		Command:           opts.Command,
-		Detach:            opts.Detach,
-		AutoRemove:        opts.Remove,
-		Writer:            os.Stdout,
-		Reader:            os.Stdin,
-		Tty:               !opts.noTty,
-		WorkingDir:        opts.workdir,
-		User:              opts.user,
-		Environment:       opts.environment,
-		Entrypoint:        entrypoint,
-		Labels:            labels,
-		UseNetworkAliases: opts.useAliases,
-		Index:             0,
+		Name:        opts.name,
+		Service:     opts.Service,
+		Command:     opts.Command,
+		Detach:      opts.Detach,
+		AutoRemove:  opts.Remove,
+		Writer:      os.Stdout,
+		Reader:      os.Stdin,
+		Tty:         !opts.noTty,
+		WorkingDir:  opts.workdir,
+		User:        opts.user,
+		Environment: opts.environment,
+		Entrypoint:  entrypoint,
+		Labels:      labels,
+		Index:       0,
 	}
 	exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts)
 	if exitCode != 0 {
diff --git a/cli/cmd/compose/top.go b/cli/cmd/compose/top.go
deleted file mode 100644
index c7d839cc..00000000
--- a/cli/cmd/compose/top.go
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-   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"
-	"io"
-	"os"
-	"sort"
-	"strings"
-	"text/tabwriter"
-
-	"github.com/spf13/cobra"
-
-	"github.com/docker/compose-cli/api/client"
-)
-
-type topOptions struct {
-	*projectOptions
-}
-
-func topCommand(p *projectOptions) *cobra.Command {
-	opts := topOptions{
-		projectOptions: p,
-	}
-	topCmd := &cobra.Command{
-		Use:   "top",
-		Short: "Display the running processes",
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runTop(cmd.Context(), opts, args)
-		},
-	}
-	return topCmd
-}
-
-func runTop(ctx context.Context, opts topOptions, services []string) error {
-	c, err := client.NewWithDefaultLocalBackend(ctx)
-	if err != nil {
-		return err
-	}
-	projectName, err := opts.toProjectName()
-	if err != nil {
-		return err
-	}
-	containers, err := c.ComposeService().Top(ctx, projectName, services)
-	if err != nil {
-		return err
-	}
-
-	sort.Slice(containers, func(i, j int) bool {
-		return containers[i].Name < containers[j].Name
-	})
-
-	for _, container := range containers {
-		fmt.Printf("%s\n", container.Name)
-		err := psPrinter(os.Stdout, func(w io.Writer) {
-			for _, proc := range container.Processes {
-				info := []interface{}{}
-				for _, p := range proc {
-					info = append(info, p)
-				}
-				_, _ = fmt.Fprintf(w, strings.Repeat("%s\t", len(info))+"\n", info...)
-
-			}
-			fmt.Fprintln(w)
-		},
-			container.Titles...)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func psPrinter(out io.Writer, printer func(writer io.Writer), headers ...string) error {
-	w := tabwriter.NewWriter(out, 5, 1, 3, ' ', 0)
-	_, _ = fmt.Fprintln(w, strings.Join(headers, "\t"))
-	printer(w)
-	return w.Flush()
-}
diff --git a/cli/cmd/version.go b/cli/cmd/version.go
index 89ea6477..878d0b1e 100644
--- a/cli/cmd/version.go
+++ b/cli/cmd/version.go
@@ -36,8 +36,12 @@ func VersionCommand() *cobra.Command {
 		Use:   "version",
 		Short: "Show the Docker version information",
 		Args:  cobra.MaximumNArgs(0),
-		Run: func(cmd *cobra.Command, _ []string) {
-			runVersion(cmd)
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := runVersion(cmd)
+			if err != nil {
+				return ExitCodeError{ExitCode: 1}
+			}
+			return nil
 		},
 	}
 	// define flags for backward compatibility with com.docker.cli
@@ -48,34 +52,38 @@ func VersionCommand() *cobra.Command {
 	return cmd
 }
 
-func runVersion(cmd *cobra.Command) {
+func runVersion(cmd *cobra.Command) error {
 	var versionString string
+	var err error
 	format := strings.ToLower(strings.ReplaceAll(cmd.Flag(formatOpt).Value.String(), " ", ""))
 	displayedVersion := strings.TrimPrefix(internal.Version, "v")
 	// Replace is preferred in this case to keep the order.
 	switch format {
 	case formatter.PRETTY, "":
-		versionString = strings.Replace(getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...),
+		versionString, err = getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...)
+		versionString = strings.Replace(versionString,
 			"\n Version:", "\n Cloud integration: "+displayedVersion+"\n Version:", 1)
 	case formatter.JSON, formatter.TemplateLegacyJSON: // Try to catch full JSON formats
-		versionString = strings.Replace(getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...),
+		versionString, err = getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...)
+		versionString = strings.Replace(versionString,
 			`"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, displayedVersion), 1)
 	default:
-		versionString = getOutFromMoby(cmd)
+		versionString, err = getOutFromMoby(cmd)
 	}
 
 	fmt.Print(versionString)
+	return err
 }
 
-func getOutFromMoby(cmd *cobra.Command, args ...string) string {
-	versionResult, _ := mobycli.ExecSilent(cmd.Context(), args...)
+func getOutFromMoby(cmd *cobra.Command, args ...string) (string, error) {
+	versionResult, err := mobycli.ExecSilent(cmd.Context(), args...)
 	// we don't want to fail on error, there is an error if the engine is not available but it displays client version info
 	// Still, technically the [] byte versionResult could be nil, just let the original command display what it has to display
 	if versionResult == nil {
 		mobycli.Exec(cmd.Root())
-		return ""
+		return "", nil
 	}
-	return string(versionResult)
+	return string(versionResult), err
 }
 
 func fixedPrettyArgs(oArgs []string) []string {
diff --git a/cli/main.go b/cli/main.go
index eb59c005..2500cf22 100644
--- a/cli/main.go
+++ b/cli/main.go
@@ -47,6 +47,8 @@ import (
 	"github.com/docker/compose-cli/cli/mobycli"
 	cliopts "github.com/docker/compose-cli/cli/options"
 
+	cliflags "github.com/docker/cli/cli/flags"
+
 	// Backend registrations
 	_ "github.com/docker/compose-cli/aci"
 	_ "github.com/docker/compose-cli/ecs"
@@ -151,10 +153,7 @@ func main() {
 	})
 
 	flags := root.Flags()
-	flags.StringVarP(&opts.LogLevel, "log-level", "l", "info", "Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\")")
-	flags.BoolVarP(&opts.Debug, "debug", "D", false, "Enable debug output in the logs")
-	flags.StringVarP(&opts.Host, "host", "H", "", "Daemon socket(s) to connect to")
-	opts.AddContextFlags(flags)
+	opts.InstallFlags(flags)
 	opts.AddConfigFlags(flags)
 	flags.BoolVarP(&opts.Version, "version", "v", false, "Print version information and quit")
 
@@ -184,18 +183,19 @@ func main() {
 	ctx, cancel := newSigContext()
 	defer cancel()
 
-	// --host and --version should immediately be forwarded to the original cli
-	if opts.Host != "" || opts.Version {
+	// --version should immediately be forwarded to the original cli
+	if opts.Version {
 		mobycli.Exec(root)
 	}
 
 	if opts.Config == "" {
 		fatal(errors.New("config path cannot be empty"))
 	}
+
 	configDir := opts.Config
 	ctx = config.WithDir(ctx, configDir)
 
-	currentContext := determineCurrentContext(opts.Context, configDir)
+	currentContext := determineCurrentContext(opts.Context, configDir, opts.Hosts)
 
 	s, err := store.New(configDir)
 	if err != nil {
@@ -213,7 +213,21 @@ func main() {
 		compose.Command(ctype),
 		volume.Command(ctype),
 	)
+	if ctype == store.DefaultContextType || ctype == store.LocalContextType {
+		cnxOptions := cliflags.CommonOptions{
+			Context:   opts.Context,
+			Debug:     opts.Debug,
+			Hosts:     opts.Hosts,
+			LogLevel:  opts.LogLevel,
+			TLS:       opts.TLS,
+			TLSVerify: opts.TLSVerify,
+		}
 
+		if opts.TLSVerify {
+			cnxOptions.TLSOptions = opts.TLSOptions
+		}
+		ctx = apicontext.WithCliOptions(ctx, cnxOptions)
+	}
 	ctx = apicontext.WithCurrentContext(ctx, currentContext)
 	ctx = store.WithContextStore(ctx, s)
 
@@ -302,15 +316,31 @@ func newSigContext() (context.Context, func()) {
 	return ctx, cancel
 }
 
-func determineCurrentContext(flag string, configDir string) string {
+func determineCurrentContext(flag string, configDir string, hosts []string) string {
+	// host and context flags cannot be both set at the same time -- the local backend enforces this when resolving hostname
+	// -H flag disables context --> set default as current
+	if len(hosts) > 0 {
+		return "default"
+	}
+	// DOCKER_HOST disables context --> set default as current
+	if _, present := os.LookupEnv("DOCKER_HOST"); present {
+		return "default"
+	}
 	res := flag
 	if res == "" {
-		config, err := config.LoadFile(configDir)
-		if err != nil {
-			fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
-			return "default"
+		// check if DOCKER_CONTEXT env variable was set
+		if _, present := os.LookupEnv("DOCKER_CONTEXT"); present {
+			res = os.Getenv("DOCKER_CONTEXT")
+		}
+
+		if res == "" {
+			config, err := config.LoadFile(configDir)
+			if err != nil {
+				fmt.Fprintln(os.Stderr, errors.Wrap(err, "WARNING"))
+				return "default"
+			}
+			res = config.CurrentContext
 		}
-		res = config.CurrentContext
 	}
 	if res == "" {
 		res = "default"
diff --git a/cli/options/options.go b/cli/options/options.go
index 764c9cb5..c65e62ae 100644
--- a/cli/options/options.go
+++ b/cli/options/options.go
@@ -17,16 +17,14 @@
 package options
 
 import (
-	apicontext "github.com/docker/compose-cli/api/context"
 	cliconfig "github.com/docker/compose-cli/cli/config"
+
+	cliflags "github.com/docker/cli/cli/flags"
 )
 
 // GlobalOpts contains the global CLI options
 type GlobalOpts struct {
-	apicontext.ContextFlags
 	cliconfig.ConfigFlags
-	Debug    bool
-	LogLevel string
-	Version  bool
-	Host     string
+	cliflags.CommonOptions
+	Version bool
 }
diff --git a/ecs/local/compose.go b/ecs/local/compose.go
index 69a157bd..b28cb8a1 100644
--- a/ecs/local/compose.go
+++ b/ecs/local/compose.go
@@ -191,7 +191,3 @@ func (e ecsLocalSimulation) Pause(ctx context.Context, project *types.Project) e
 func (e ecsLocalSimulation) UnPause(ctx context.Context, project *types.Project) error {
 	return e.compose.UnPause(ctx, project)
 }
-
-func (e ecsLocalSimulation) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	return e.compose.Top(ctx, projectName, services)
-}
diff --git a/ecs/top.go b/ecs/top.go
deleted file mode 100644
index 569a7c65..00000000
--- a/ecs/top.go
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-   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 ecs
-
-import (
-	"context"
-
-	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/errdefs"
-)
-
-func (b *ecsAPIService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	return nil, errdefs.ErrNotImplemented
-}
diff --git a/go.mod b/go.mod
index 89b53674..649e0bec 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/awslabs/goformation/v4 v4.15.6
 	github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v0.0.0-20210305065119-876e630c5382
+	github.com/compose-spec/compose-go v0.0.0-20210302161709-a66de601f6e2
 	github.com/containerd/console v1.0.1
 	github.com/containerd/containerd v1.4.3
 	github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect
diff --git a/go.sum b/go.sum
index 070d7126..9bb54c6b 100644
--- a/go.sum
+++ b/go.sum
@@ -305,8 +305,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
-github.com/compose-spec/compose-go v0.0.0-20210305065119-876e630c5382 h1:uKdXQudOB+DsiwDoyUR3in3uPWBk2DMxN36KlHYPjTY=
-github.com/compose-spec/compose-go v0.0.0-20210305065119-876e630c5382/go.mod h1:iz28nqFmtFuYQxfzPb7zEceImHoVrWoumcBPQutHnQ8=
+github.com/compose-spec/compose-go v0.0.0-20210302161709-a66de601f6e2 h1:gQHeYKe3H5IvvLeWsWjOlCxpo0DV6uGdCt4Q3Gr3fTg=
+github.com/compose-spec/compose-go v0.0.0-20210302161709-a66de601f6e2/go.mod h1:flNthwF3kg+JioxATZWSsuuA2N3zGGwggDNNGoE9PHA=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=
@@ -845,9 +845,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/imdario/mergo v0.3.12-0.20210303221052-29fb3d3bdc55 h1:Z+928KmcMGWCKohke8GwDHOGQSlFQi2dg3uUbD5WDJA=
-github.com/imdario/mergo v0.3.12-0.20210303221052-29fb3d3bdc55/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
diff --git a/kube/compose.go b/kube/compose.go
index 755984d1..4456069e 100644
--- a/kube/compose.go
+++ b/kube/compose.go
@@ -254,7 +254,3 @@ func (s *composeService) Pause(ctx context.Context, project *types.Project) erro
 func (s *composeService) UnPause(ctx context.Context, project *types.Project) error {
 	return errdefs.ErrNotImplemented
 }
-
-func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	return nil, errdefs.ErrNotImplemented
-}
diff --git a/local/backend.go b/local/backend.go
index 0e5ea277..fa79f1eb 100644
--- a/local/backend.go
+++ b/local/backend.go
@@ -19,16 +19,20 @@ package local
 import (
 	"context"
 
-	"github.com/docker/docker/client"
-
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose-cli/api/backend"
 	"github.com/docker/compose-cli/api/cloud"
+
 	"github.com/docker/compose-cli/api/compose"
+	apiconfig "github.com/docker/compose-cli/api/config"
 	"github.com/docker/compose-cli/api/containers"
+	apicontext "github.com/docker/compose-cli/api/context"
 	"github.com/docker/compose-cli/api/resources"
 	"github.com/docker/compose-cli/api/secrets"
 	"github.com/docker/compose-cli/api/volumes"
 	local_compose "github.com/docker/compose-cli/local/compose"
+
+	cliconfig "github.com/docker/cli/cli/config"
 )
 
 type local struct {
@@ -42,7 +46,13 @@ func init() {
 }
 
 func service(ctx context.Context) (backend.Service, error) {
-	apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+	options := apicontext.CliOptions(ctx)
+	config := apiconfig.Dir(ctx)
+	configFile, err := cliconfig.Load(config)
+	if err != nil {
+		return nil, err
+	}
+	apiClient, err := command.NewAPIClientFromFlags(&options, configFile)
 	if err != nil {
 		return nil, err
 	}
diff --git a/local/compose/convergence.go b/local/compose/convergence.go
index a443de61..fe0571a5 100644
--- a/local/compose/convergence.go
+++ b/local/compose/convergence.go
@@ -64,7 +64,7 @@ func (s *composeService) ensureScale(ctx context.Context, project *types.Project
 			number := next + i
 			name := getContainerName(project.Name, service, number)
 			eg.Go(func() error {
-				return s.createContainer(ctx, project, service, name, number, false, true)
+				return s.createContainer(ctx, project, service, name, number, false)
 			})
 		}
 	}
@@ -197,11 +197,11 @@ func getScale(config types.ServiceConfig) (int, error) {
 	return scale, err
 }
 
-func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool, useNetworkAliases bool) error {
+func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool) error {
 	w := progress.ContextWriter(ctx)
 	eventName := "Container " + name
 	w.Event(progress.CreatingEvent(eventName))
-	err := s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases)
+	err := s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove)
 	if err != nil {
 		return err
 	}
@@ -231,7 +231,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 	if inherit {
 		inherited = &container
 	}
-	err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true)
+	err = s.createMobyContainer(ctx, project, service, name, number, inherited, false)
 	if err != nil {
 		return err
 	}
@@ -268,10 +268,8 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co
 	return nil
 }
 
-func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int,
-	inherit *moby.Container,
-	autoRemove bool,
-	useNetworkAliases bool) error {
+func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, inherit *moby.Container,
+	autoRemove bool) error {
 	cState, err := GetContextContainerState(ctx)
 	if err != nil {
 		return err
@@ -289,16 +287,9 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 		Labels: containerConfig.Labels,
 	}
 	cState.Add(createdContainer)
-	for netName, cfg := range service.Networks {
+	for netName := range service.Networks {
 		netwrk := project.Networks[netName]
-		aliases := []string{getContainerName(project.Name, service, number)}
-		if useNetworkAliases {
-			aliases = append(aliases, service.Name)
-			if cfg != nil {
-				aliases = append(aliases, cfg.Aliases...)
-			}
-		}
-		err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, aliases...)
+		err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, service.Name, getContainerName(project.Name, service, number))
 		if err != nil {
 			return err
 		}
diff --git a/local/compose/run.go b/local/compose/run.go
index 14a1f971..d29f07f1 100644
--- a/local/compose/run.go
+++ b/local/compose/run.go
@@ -62,7 +62,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
 	if err := s.waitDependencies(ctx, project, service); err != nil {
 		return 0, err
 	}
-	if err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove, opts.UseNetworkAliases); err != nil {
+	if err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove); err != nil {
 		return 0, err
 	}
 	containerID := service.ContainerName
diff --git a/local/compose/top.go b/local/compose/top.go
deleted file mode 100644
index 24549e99..00000000
--- a/local/compose/top.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-   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"
-
-	"github.com/docker/compose-cli/api/compose"
-	moby "github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/filters"
-	"golang.org/x/sync/errgroup"
-)
-
-func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
-	var containers Containers
-	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-		Filters: filters.NewArgs(projectFilter(projectName)),
-	})
-	if err != nil {
-		return nil, err
-	}
-	if len(services) > 0 {
-		containers = containers.filter(isService(services...))
-	}
-	summary := make([]compose.ContainerProcSummary, len(containers))
-	eg, ctx := errgroup.WithContext(ctx)
-	for i, c := range containers {
-		container := c
-		i := i
-		eg.Go(func() error {
-			topContent, err := s.apiClient.ContainerTop(ctx, container.ID, []string{})
-			if err != nil {
-				return err
-			}
-			summary[i] = compose.ContainerProcSummary{
-				ID:        container.ID,
-				Name:      getCanonicalContainerName(container),
-				Processes: topContent.Processes,
-				Titles:    topContent.Titles,
-			}
-			return nil
-		})
-	}
-	return summary, eg.Wait()
-}
diff --git a/local/containers.go b/local/containers.go
index e3e72d25..f2b28738 100644
--- a/local/containers.go
+++ b/local/containers.go
@@ -39,10 +39,11 @@ import (
 )
 
 type containerService struct {
-	apiClient *client.Client
+	apiClient client.APIClient
 }
 
 func (cs *containerService) Inspect(ctx context.Context, id string) (containers.Container, error) {
+
 	c, err := cs.apiClient.ContainerInspect(ctx, id)
 	if err != nil {
 		return containers.Container{}, err
diff --git a/local/volumes.go b/local/volumes.go
index 2fbf08b7..9b49aaf4 100644
--- a/local/volumes.go
+++ b/local/volumes.go
@@ -29,7 +29,7 @@ import (
 )
 
 type volumeService struct {
-	apiClient *client.Client
+	apiClient client.APIClient
 }
 
 func (vs *volumeService) List(ctx context.Context) ([]volumes.Volume, error) {
diff --git a/scripts/install/install_linux.sh b/scripts/install/install_linux.sh
index 54126d37..ace08caa 100644
--- a/scripts/install/install_linux.sh
+++ b/scripts/install/install_linux.sh
@@ -106,11 +106,7 @@ if ! [ "$(command -v curl)" ]; then
 	exit 1
 fi
 
-if [ "$(uname -m)" = "aarch64" ]; then
-	DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-arm64.tar.gz" | cut -d : -f 2,3)}
-else
-	DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-amd64.tar.gz" | cut -d : -f 2,3)}
-fi
+DOWNLOAD_URL=${DOWNLOAD_URL:-$(curl -s ${RELEASE_URL} | grep "browser_download_url.*docker-linux-amd64.tar.gz" | cut -d : -f 2,3)}
 
 # Check if the Compose CLI is already installed
 if [ $(is_new_cli "docker") -eq 1 ]; then


Test patch:
diff --git a/cli/main_test.go b/cli/main_test.go
index bb62ee04..3a0ee145 100644
--- a/cli/main_test.go
+++ b/cli/main_test.go
@@ -44,20 +44,24 @@ func TestDetermineCurrentContext(t *testing.T) {
 	assert.NilError(t, err)
 
 	// If nothing set, fallback to default
-	c := determineCurrentContext("", "")
+	c := determineCurrentContext("", "", []string{})
 	assert.Equal(t, c, "default")
 
 	// If context flag set, use that
-	c = determineCurrentContext("other-context", "")
+	c = determineCurrentContext("other-context", "", []string{})
 	assert.Equal(t, c, "other-context")
 
 	// If no context flag, use config
-	c = determineCurrentContext("", d)
+	c = determineCurrentContext("", d, []string{})
 	assert.Equal(t, c, "some-context")
 
 	// Ensure context flag overrides config
-	c = determineCurrentContext("other-context", d)
+	c = determineCurrentContext("other-context", d, []string{})
 	assert.Equal(t, "other-context", c)
+
+	// Ensure host flag overrides context
+	c = determineCurrentContext("other-context", d, []string{"hostname"})
+	assert.Equal(t, "default", c)
 }
 
 func TestCheckOwnCommand(t *testing.T) {
diff --git a/local/e2e/cli-only/e2e_test.go b/local/e2e/cli-only/e2e_test.go
index 40faf6f9..689cc56b 100644
--- a/local/e2e/cli-only/e2e_test.go
+++ b/local/e2e/cli-only/e2e_test.go
@@ -396,15 +396,14 @@ func TestLegacy(t *testing.T) {
 	})
 
 	t.Run("host flag", func(t *testing.T) {
-		stderr := "Cannot connect to the Docker daemon at tcp://localhost:123"
+		stderr := "dial tcp: lookup nonexistent"
 		if runtime.GOOS == "windows" {
-			stderr = "error during connect: Get http://localhost:123"
+			stderr = "dial tcp: lookup nonexistent: no such host"
 		}
-		res := c.RunDockerOrExitError("-H", "tcp://localhost:123", "version")
-		res.Assert(t, icmd.Expected{
-			ExitCode: 1,
-			Err:      stderr,
-		})
+		res := c.RunDockerOrExitError("-H", "tcp://nonexistent:123", "version")
+		assert.Assert(t, res.ExitCode == 1)
+		assert.Assert(t, strings.Contains(res.Stdout(), stderr), res.Stdout())
+
 	})
 
 	t.Run("existing contexts delegate", func(t *testing.T) {
diff --git a/local/e2e/compose/compose_run_test.go b/local/e2e/compose/compose_run_test.go
index 59fb75e9..777d3869 100644
--- a/local/e2e/compose/compose_run_test.go
+++ b/local/e2e/compose/compose_run_test.go
@@ -17,7 +17,6 @@
 package e2e
 
 import (
-	"os"
 	"strings"
 	"testing"
 
@@ -84,20 +83,4 @@ func TestLocalComposeRun(t *testing.T) {
 		res := c.RunDockerCmd("ps", "--all")
 		assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
 	})
-
-	t.Run("compose run --volumes", func(t *testing.T) {
-		wd, err := os.Getwd()
-		assert.NilError(t, err)
-		res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo")
-		res.Assert(t, icmd.Expected{Out: "compose_run_test.go"})
-
-		res = c.RunDockerCmd("ps", "--all")
-		assert.Assert(t, strings.Contains(res.Stdout(), "run-test_back"), res.Stdout())
-	})
-
-	t.Run("compose run --publish", func(t *testing.T) {
-		c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--rm", "--publish", "8080:80", "-d", "back", "/bin/sh", "-c", "sleep 10")
-		res := c.RunDockerCmd("ps")
-		assert.Assert(t, strings.Contains(res.Stdout(), "8080->80/tcp"), res.Stdout())
-	})
 }
diff --git a/local/e2e/compose/compose_test.go b/local/e2e/compose/compose_test.go
index 967f2b6d..dc8008f2 100644
--- a/local/e2e/compose/compose_test.go
+++ b/local/e2e/compose/compose_test.go
@@ -65,14 +65,6 @@ func TestLocalComposeUp(t *testing.T) {
 		res.Assert(t, icmd.Expected{Out: projectName + "_default"})
 	})
 
-	t.Run("top", func(t *testing.T) {
-		res := c.RunDockerCmd("compose", "-p", projectName, "top")
-		output := res.Stdout()
-		assert.Assert(t, strings.Contains(output, `UID    PID     PPID    C    STIME   TTY   TIME       CMD`))
-		assert.Assert(t, strings.Contains(output, `java -Xmx8m -Xms8m -jar /app/words.jar`))
-		assert.Assert(t, strings.Contains(output, `/dispatcher`))
-	})
-
 	t.Run("check compose labels", func(t *testing.T) {
 		wd, _ := os.Getwd()
 		res := c.RunDockerCmd("inspect", projectName+"_web_1")