An image is a lightweight, stand-alone, executable package that includes everything needed to run a piece of software, including the code, a runtime, libraries, environment variables, and config files.
A container is a runtime instance of an image—what the image becomes in memory when actually executed. It runs completely isolated from the host environment by default, only accessing host files and ports if configured to do so.
Containers run apps natively on the host machine’s kernel. They have better performance characteristics than virtual machines that only get virtual access to host resources through a hypervisor. Containers can get native access, each one running in a discrete process, taking no more memory than any other executable.
Source:https://docs.docker.com/engine/getstarted/step_four/#step-3-learn-about-the-build-process
Build the app
We are ready to build the app. Make sure you are still at the top level of your new directory. Here’s what ls
should show:
$ ls
Dockerfile app.py requirements.txt
Now run the build command. This creates a Docker image, which we’re going to tag using -t
so it has a friendly name.
docker build -t friendlyhello .
Where is your built image? It’s in your machine’s local Docker image registry:
$ docker images
REPOSITORY TAG IMAGE ID
friendlyhello latest 326387cea398
Tip: You can use the commands docker images
or the newer docker image ls
list images. They give you the same output.
Run the app
Run the app, mapping your machine’s port 4000 to the container’s published port 80 using -p
:
docker run -p 4000:80 friendlyhello
You should see a message that Python is serving your app at http://0.0.0.0:80
. But that message is coming from inside the container, which doesn’t know you mapped port 80 of that container to 4000, making the correct URL http://localhost:4000
.
Go to that URL in a web browser to see the display content served up on a web page, including “Hello World” text, the container ID, and the Redis error message.
Note: If you are using Docker Toolbox on Windows 7, use the Docker Machine IP instead of localhost
. For example, http://192.168.99.100:4000/. To find the IP address, use the command docker-machine ip
.
You can also use the curl
command in a shell to view the same content.
$ curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>
This port remapping of 4000:80
is to demonstrate the difference between what you EXPOSE
within the Dockerfile
, and what you publish
using docker run -p
. In later steps, we’ll just map port 80 on the host to port 80 in the container and use http://localhost
.
Hit CTRL+C
in your terminal to quit.
On Windows, explicitly stop the container
On Windows systems, CTRL+C
does not stop the container. So, first type CTRL+C
to get the prompt back (or open another shell), then type docker container ls
to list the running containers, followed by docker container stop <Container NAME or ID>
to stop the container. Otherwise, you’ll get an error response from the daemon when you try to re-run the container in the next step.
Now let’s run the app in the background, in detached mode:
docker run -d -p 4000:80 friendlyhello
You get the long container ID for your app and then are kicked back to your terminal. Your container is running in the background. You can also see the abbreviated container ID with docker container ls
(and both work interchangeably when running commands):
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED
1fa4ab2cf395 friendlyhello "python app.py" 28 seconds ago
You’ll see that CONTAINER ID
matches what’s on http://localhost:4000
.
Now use docker container stop
to end the process, using the CONTAINER ID
, like so:
docker container stop 1fa4ab2cf395
Share your image
To demonstrate the portability of what we just created, let’s upload our built image and run it somewhere else. After all, you’ll need to learn how to push to registries when you want to deploy containers to production.
A registry is a collection of repositories, and a repository is a collection of images—sort of like a GitHub repository, except the code is already built. An account on a registry can create many repositories. The docker
CLI uses Docker’s public registry by default.
Note: We’ll be using Docker’s public registry here just because it’s free and pre-configured, but there are many public ones to choose from, and you can even set up your own private registry using
Docker Trusted Registry.
Log in with your Docker ID
If you don’t have a Docker account, sign up for one at
cloud.docker.com. Make note of your username.
Log in to the Docker public registry on your local machine.
Tag the image
The notation for associating a local image with a repository on a registry is username/repository:tag
. The tag is optional, but recommended, since it is the mechanism that registries use to give Docker images a version. Give the repository and tag meaningful names for the context, such as get-started:part2
. This will put the image in the get-started
repository and tag it as part2
.
Now, put it all together to tag the image. Run docker tag image
with your username, repository, and tag names so that the image will upload to your desired destination. The syntax of the command is:
docker tag image username/repository:tag
For example:
docker tag friendlyhello john/get-started:part2
Run
docker images to see your newly tagged image. (You can also use
docker image ls
.)
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
friendlyhello latest d9e555c53008 3 minutes ago 195MB
john/get-started part2 d9e555c53008 3 minutes ago 195MB
python 2.7-slim 1c7128a655f6 5 days ago 183MB
...
Publish the image
Upload your tagged image to the repository:
docker push username/repository:tag
Once complete, the results of this upload are publicly available. If you log in to
Docker Hub, you will see the new image there, with its pull command.
Pull and run the image from the remote repository
From now on, you can use docker run
and run your app on any machine with this command:
docker run -p 4000:80 username/repository:tag
If the image isn’t available locally on the machine, Docker will pull it from the repository.
$ docker run -p 4000:80 john/get-started:part2
Unable to find image 'john/get-started:part2' locally
part2: Pulling from john/get-started
10a267c67f42: Already exists
f68a39a6a5e4: Already exists
9beaffc0cf19: Already exists
3c1fe835fb6b: Already exists
4c9f1fa8fcb8: Already exists
ee7d8f576a14: Already exists
fbccdcced46e: Already exists
Digest: sha256:0601c866aab2adcc6498200efd0f754037e909e5fd42069adeff72d1e2439068
Status: Downloaded newer image for john/get-started:part2
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
Note: If you don’t specify the :tag
portion of these commands, the tag of :latest
will be assumed, both when you build and when you run images. Docker will use the last version of the image that ran without a tag specified (not necessarily the most recent image).
No matter where docker run
executes, it pulls your image, along with Python and all the dependencies from requirements.txt
, and runs your code. It all travels together in a neat little package, and the host machine doesn’t have to install anything but Docker to run it.
Get Started, Part 3: Services
Estimated reading time: 8 minutes
Prerequisites
-
-
Read the orientation in
Part 1.
Learn how to create containers in
Part 2.
Make sure you have published the
friendlyhello
image you created by
pushing it to a registry. We’ll use that shared image here.
Be sure your image works as a deployed container. Run this command, slotting in your info for username
, repo
, and tag
: docker run -p 80:80 username/repo:tag
, then visit http://localhost/
.
Introduction
In part 3, we scale our application and enable load-balancing. To do this, we must go one level up in the hierarchy of a distributed application: the service.
- Stack
- Services (you are here)
- Container (covered in part 2)
About services
In a distributed application, different pieces of the app are called “services.” For example, if you imagine a video sharing site, it probably includes a service for storing application data in a database, a service for video transcoding in the background after a user uploads something, a service for the front-end, and so on.
Services are really just “containers in production.” A service only runs one image, but it codifies the way that image runs—what ports it should use, how many replicas of the container should run so the service has the capacity it needs, and so on. Scaling a service changes the number of container instances running that piece of software, assigning more computing resources to the service in the process.
Luckily it’s very easy to define, run, and scale services with the Docker platform – just write a docker-compose.yml
file.
Your first docker-compose.yml
file
A docker-compose.yml
file is a YAML file that defines how Docker containers should behave in production.
docker-compose.yml
Save this file as
docker-compose.yml
wherever you want. Be sure you have
pushed the image you created in
Part 2 to a registry, and update this
.yml
by replacing
username/repo:tag
with your image details.
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: username/repo:tag
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "80:80"
networks:
- webnet
networks:
webnet:
This docker-compose.yml
file tells Docker to do the following:
-
Run 5 instances of that image as a service called web
, limiting each one to use, at most, 10% of the CPU (across all cores), and 50MB of RAM.
Immediately restart containers if one fails.
Map port 80 on the host to web
’s port 80.
Instruct web
’s containers to share port 80 via a load-balanced network called webnet
. (Internally, the containers themselves will publish to web
’s port 80 at an ephemeral port.)
Define the webnet
network with the default settings (which is a load-balanced overlay network).
Run your new load-balanced app
Before we can use the docker stack deploy
command we’ll first run:
Note: We’ll get into the meaning of that command in
part 4. If you don’t run
docker swarm init
you’ll get an error that “this node is not a swarm manager.”
Now let’s run it. You have to give your app a name. Here, it is set to getstartedlab
:
docker stack deploy -c docker-compose.yml getstartedlab
Our single service stack is running 5 container instances of our deployed image on one host. Let’s investigate.
Get the service ID for the one service in our application:
You’ll see output for the web
service, prepended with your app name. If you named it the same as shown in this example, the name will be getstartedlab_web
. The service ID is listed as well, along with the number of replicas, image name, and exposed ports.
A single container running in a service is called a task. Tasks are given unique IDs that numerically increment, up to the number of replicas
you defined in docker-compose.yml
. List the tasks for your service:
docker service ps getstartedlab_web
Tasks also show up if you just list all the containers on your system, though that will not be filtered by service:
You can run curl -4 http://localhost
several times in a row, or go to that URL in your browser and hit refresh a few times.
Either way, you’ll see the container ID change, demonstrating the load-balancing; with each request, one of the 5 tasks is chosen, in a round-robin fashion, to respond. The container IDs will match your output from the previous command (docker container ls -q
).
Running Windows 10?
Windows 10 PowerShell should already have
curl
available, but if not you can grab a Linux terminal emulater like
Git BASH, or download
wget for Windows which is very similar.
Slow response times?
Depending on your environment’s networking configuration, it may take up to 30 seconds for the containers to respond to HTTP requests. This is not indicative of Docker or swarm performance, but rather an unmet Redis dependency that we will address later in the tutorial. For now, the visitor counter isn’t working for the same reason; we haven’t yet added a service to persist data.
Scale the app
You can scale the app by changing the replicas
value in docker-compose.yml
, saving the change, and re-running the docker stack deploy
command:
docker stack deploy -c docker-compose.yml getstartedlab
Docker will do an in-place update, no need to tear the stack down first or kill any containers.
Now, re-run docker container ls -q
to see the deployed instances reconfigured. If you scaled up the replicas, more tasks, and hence, more containers, are started.
Take down the app and the swarm
It’s as easy as that to stand up and scale your app with Docker. You’ve taken a huge step towards learning how to run containers in production. Up next, you will learn how to run this app as a bonafide swarm on a cluster of Docker machines.
Note: Compose files like this are used to define applications with Docker, and can be uploaded to cloud providers using
Docker Cloud, or on any hardware or cloud provider you choose with
Docker Enterprise Edition.
Recap and cheat sheet (optional)
To recap, while typing docker run
is simple enough, the true implementation of a container in production is running it as a service. Services codify a container’s behavior in a Compose file, and this file can be used to scale, limit, and redeploy our app. Changes to the service can be applied in place, as it runs, using the same command that launched the service: docker stack deploy
.
Some commands to explore at this stage:
docker stack ls # List stacks or apps
docker stack deploy -c <composefile> <appname> # Run the specified Compose file
docker service ls # List running services associated with an app
docker service ps <service> # List tasks associated with an app
docker inspect <task or container> # Inspect task or container
docker container ls -q # List container IDs
docker stack rm <appname> # Tear down an application
docker swarm leave --force # Take down a single node swarm from the manager
Get Started, Part 4: Swarms
Estimated reading time: 18 minutes
Prerequisites
-
-
-
Read the orientation in
Part 1.
Learn how to create containers in
Part 2.
Make sure you have published the
friendlyhello
image you created by
pushing it to a registry. We’ll be using that shared image here.
Be sure your image works as a deployed container. Run this command, slotting in your info for username
, repo
, and tag
: docker run -p 80:80 username/repo:tag
, then visit http://localhost/
.
Have a copy of your
docker-compose.yml
from
Part 3 handy.
Introduction
In
part 3, you took an app you wrote in
part 2, and defined how it should run in production by turning it into a service, scaling it up 5x in the process.
Here in part 4, you deploy this application onto a cluster, running it on multiple machines. Multi-container, multi-machine applications are made possible by joining multiple machines into a “Dockerized” cluster called a swarm.
Understanding Swarm clusters
A swarm is a group of machines that are running Docker and joined into a cluster. After that has happened, you continue to run the Docker commands you’re used to, but now they are executed on a cluster by a swarm manager. The machines in a swarm can be physical or virtual. After joining a swarm, they are referred to as nodes.
Swarm managers can use several strategies to run containers, such as “emptiest node” – which fills the least utilized machines with containers. Or “global”, which ensures that each machine gets exactly one instance of the specified container. You instruct the swarm manager to use these strategies in the Compose file, just like the one you have already been using.
Swarm managers are the only machines in a swarm that can execute your commands, or authorize other machines to join the swarm as workers. Workers are just there to provide capacity and do not have the authority to tell any other machine what it can and cannot do.
Up until now, you have been using Docker in a single-host mode on your local machine. But Docker also can be switched into swarm mode, and that’s what enables the use of swarms. Enabling swarm mode instantly makes the current machine a swarm manager. From then on, Docker will run the commands you execute on the swarm you’re managing, rather than just on the current machine.
Set up your swarm
A swarm is made up of multiple nodes, which can be either physical or virtual machines. The basic concept is simple enough: run docker swarm init
to enable swarm mode and make your current machine a swarm manager, then run docker swarm join
on other machines to have them join the swarm as workers. Choose a tab below to see how this plays out in various contexts. We’ll use VMs to quickly create a two-machine cluster and turn it into a swarm.
Create a cluster
VMS ON YOUR LOCAL MACHINE (MAC, LINUX, WINDOWS 7 AND 8)
Note: If you are on a Windows system that has Hyper-V installed, such as Windows 10, there is no need to install VirtualBox and you should use Hyper-V instead. View the instructions for Hyper-V systems by clicking the Hyper-V tab above. If you are using
Docker Toolbox, you should already have VirtualBox installed as part of it, so you are good to go.
Now, create a couple of VMs using docker-machine
, using the VirtualBox driver:
docker-machine create --driver virtualbox myvm1
docker-machine create --driver virtualbox myvm2
LIST THE VMS AND GET THEIR IP ADDRESSES
You now have two VMs created, named myvm1
and myvm2
.
Use this command to list the machines and get their IP addresses.
Here is example output from this command.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v17.06.2-ce
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v17.06.2-ce
INITIALIZE THE SWARM AND ADD NODES
The first machine will act as the manager, which executes management commands and authenticates workers to join the swarm, and the second will be a worker.
You can send commands to your VMs using docker-machine ssh
. Instruct myvm1
to become a swarm manager with docker swarm init
and you’ll see output like this:
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr <myvm1 ip>"
Swarm initialized: current node <node ID> is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token <token> \
<myvm ip>:<port>
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Ports 2377 and 2376
Always run docker swarm init
and docker swarm join
with port 2377 (the swarm management port), or no port at all and let it take the default.
The machine IP addresses returned by
docker-machine ls
include port 2376, which is the Docker daemon port. Do not use this port or
you may experience errors.
As you can see, the response to docker swarm init
contains a pre-configured docker swarm join
command for you to run on any nodes you want to add. Copy this command, and send it to myvm2
via docker-machine ssh
to have myvm2
join your new swarm as a worker:
$ docker-machine ssh myvm2 "docker swarm join \
--token <token> \
<ip>:2377"
This node joined a swarm as a worker.
Congratulations, you have created your first swarm!
Run docker node ls
on the manager to view the nodes in this swarm:
$ docker-machine ssh myvm1 "docker node ls"
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
brtu9urxwfd5j0zrmkubhpkbd myvm2 Ready Active
rihwohkh3ph38fhillhhb84sk * myvm1 Ready Active Leader
Leaving a swarm
If you want to start over, you can run docker swarm leave
from each node.
Deploy your app on the swarm cluster
The hard part is over. Now you just repeat the process you used in
part 3 to deploy on your new swarm. Just remember that only swarm managers like
myvm1
execute Docker commands; workers are just for capacity.
So far, you’ve been wrapping Docker commmands in docker-machine ssh
to talk to the VMs. Another option is to run docker-machine env <machine>
to get and run a command that configures your current shell to talk to the Docker daemon on the VM. This method works better for the next step because it allows you to use your local docker-compose.yml
file to deploy the app “remotely” without having to copy it anywhere.
Type docker-machine env myvm1
, then copy-paste and run the command provided as the last line of the output to configure your shell to talk to myvm1
, the swarm manager.
The commands to configure your shell differ depending on whether you are Mac, Linux, or Windows, so examples of each are shown on the tabs below.
DOCKER MACHINE SHELL ENVIRONMENT ON MAC OR LINUX
Run docker-machine env myvm1
to get the command to configure your shell to talk to myvm1
.
$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/sam/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)
Run the given command to configure your shell to talk to myvm1
.
eval $(docker-machine env myvm1)
Run docker-machine ls
to verify that myvm1
is now the active machine, as indicated by the asterisk next to it.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 * virtualbox Running tcp://192.168.99.100:2376 v17.06.2-ce
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v17.06.2-ce
Deploy the app on the swarm manager
Now that you have my myvm1
, you can use its powers as a swarm manager to deploy your app by using the same docker stack deploy
command you used in part 3 to myvm1
, and your local copy of docker-compose.yml.
You are connected to
myvm1
by means of the
docker-machine
shell configuration, and you still have access to the files on your local host. Make sure you are in the same directory as before, which includes the
docker-compose.yml
file you created in part 3.
Just like before, run the following command to deploy the app on myvm1
.
docker stack deploy -c docker-compose.yml getstartedlab
And that’s it, the app is deployed on a swarm cluster!
Now you can use the same
docker commands you used in part 3. Only this time you’ll see that the services (and associated containers) have been distributed between both
myvm1
and
myvm2
.
$ docker stack ps getstartedlab
ID NAME IMAGE NODE DESIRED STATE
jq2g3qp8nzwx getstartedlab_web.1 john/get-started:part2 myvm1 Running
88wgshobzoxl getstartedlab_web.2 john/get-started:part2 myvm2 Running
vbb1qbkb0o2z getstartedlab_web.3 john/get-started:part2 myvm2 Running
ghii74p9budx getstartedlab_web.4 john/get-started:part2 myvm1 Running
0prmarhavs87 getstartedlab_web.5 john/get-started:part2 myvm2 Running
Connecting to VMs with docker-machine env
and docker-machine ssh
This tutorial demos both docker-machine ssh
and docker-machine env
, since these are available on all platforms via the docker-machine
CLI.
Accessing your cluster
You can access your app from the IP address of either myvm1
or myvm2
.
The network you created is shared between them and load-balancing. Run docker-machine ls
to get your VMs’ IP addresses and visit either of them on a browser, hitting refresh (or just curl
them).
You’ll see five possible container IDs all cycling by randomly, demonstrating the load-balancing.
The reason both IP addresses work is that nodes in a swarm participate in an ingress routing mesh. This ensures that a service deployed at a certain port within your swarm always has that port reserved to itself, no matter what node is actually running the container. Here’s a diagram of how a routing mesh for a service called my-web
published at port 8080
on a three-node swarm would look:
Having connectivity trouble?
Keep in mind that in order to use the ingress network in the swarm, you need to have the following ports open between the swarm nodes before you enable swarm mode:
- Port 7946 TCP/UDP for container network discovery.
- Port 4789 UDP for the container ingress network.
Iterating and scaling your app
From here you can do everything you learned about in parts 2 and 3.
Scale the app by changing the docker-compose.yml
file.
Change the app behavior by editing code, then rebuild, and push the new image. (To do this, follow the same steps you took earlier to
build the app and
publish the image).
In either case, simply run docker stack deploy
again to deploy these changes.
You can join any machine, physical or virtual, to this swarm, using the same docker swarm join
command you used on myvm2
, and capacity will be added to your cluster. Just run docker stack deploy
afterwards, and your app will take advantage of the new resources.
Cleanup and reboot
Stacks and swarms
You can tear down the stack with docker stack rm
. For example:
docker stack rm getstartedlab
Keep the swarm or remove it?
At some point later, you can remove this swarm if you want to withdocker-machine ssh myvm2 "docker swarm leave"
on the worker and docker-machine ssh myvm1 "docker swarm leave --force"
on the manager, but you’ll need this swarm for part 5, so please keep it around for now.
Unsetting docker-machine shell variable settings
You can unset the docker-machine
environment variables in your current shell with the following command:
eval $(docker-machine env -u)
This disconnects the shell from
docker-machine
created virtual machines, and allows you to continue working in the same shell, now using native
docker
commands (for example, on Docker for Mac or Docker for Windows). To learn more, see the
Machine topic on unsetting environment variables.
Restarting Docker machines
If you shut down your local host, Docker machines will stop running. You can check the status of machines by running docker-machine ls
.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Stopped Unknown
myvm2 - virtualbox Stopped Unknown
To restart a machine that’s stopped, run:
docker-machine start <machine-name>
For example:
$ docker-machine start myvm1
Starting "myvm1"...
(myvm1) Check network to re-create if needed...
(myvm1) Waiting for an IP...
Machine "myvm1" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
$ docker-machine start myvm2
Starting "myvm2"...
(myvm2) Check network to re-create if needed...
(myvm2) Waiting for an IP...
Machine "myvm2" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
Recap and cheat sheet (optional)
In part 4 you learned what a swarm is, how nodes in swarms can be managers or workers, created a swarm, and deployed an application on it. You saw that the core Docker commands didn’t change from part 3, they just had to be targeted to run on a swarm master. You also saw the power of Docker’s networking in action, which kept load-balancing requests across containers, even though they were running on different machines. Finally, you learned how to iterate and scale your app on a cluster.
Here are some commands you might like to run to interact with your swarm and your VMs a bit:
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 # View basic information about your node
docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect <node ID>" # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token
docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end
docker node ls # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 # Start a VM that is currently not running
docker-machine env myvm1 # show environment variables and command for myvm1
eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1
docker stack deploy -c <file> <app> # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images