Docker Compose Up vs Start and Down vs Stop: Differences Explained

If you are new to Docker Compose and learning it by following various tutorials, you might come across the terms like docker-compose up, docker-compose up -d, docker-compose stop, docker-compose down, or even docker-compose stop.

These terms are enough to confuse a docker beginner because many of these docker-compose commands seem to behave in a very similar fashion.

In fact, it can be especially difficult at first to right away tell the difference between docker-compose up and docker-compose start.

Isn’t starting a container via Docker Compose the same as running the up command? Not exactly.

Let me explain it all to you in detail.

Differences between Docker Compose up, up -d, stop, start, down and down -v

What these commands do:

Docker Compose up command deploys web app services and creates fresh new containers from the docker image along with setting up networks, volumes and every configuration specified within the Docker Compose file. When you specify -d, it means you tell it to run it in detached mode so that it runs in the background by giving you the control of the terminal (discussed in a moment via some examples below).

Docker Compose stop command stops all services associated with a Docker Compose configuration. It does NOT remove any containers or associated internal volumes or networks.

Docker Compose start command will start any stopped services as were specified on a stopped configuration based on the same Docker Compose file.

Docker Compose down command stops all services associated with a Docker Compose configuration. Unlike stop, it also removes any containers and internal networks associated with the services. But NOT internally specified volumes. To do that as well, you need to additionally specify the -v flag after the down command.

This sounds similar to the Docker run vs start command, right?

Docker Run vs Start vs Create: Difference Explained
For a docker beginner, terms like docker start, docker run and docker create could be confusing. This article explains the difference with examples.

Enough theory, let's look at some hands-on examples now.

Understanding the difference with hands-on example

If you want to follow the examples, make sure that you have Docker and Docker Compose installer already.

Let’s say that you are using a Docker Compose based Ghost blog setup running on your Linux server.

In all our self-host tutorials, I mostly tend to use the -d flag whenever we deploy our configurations on our servers. But what if you don't specify it?

avimanyu@localhost:~/ghost$ docker-compose up
Pulling ghost (ghost:4.20.3)...
4.20.3: Pulling from library/ghost
b380bbd43752: Pull complete
8d36a6ce056a: Pull complete
f75fe68b8e22: Pull complete
44f6d143e12f: Pull complete
0ebe8063dedd: Pull complete
f984e0e37c5a: Pull complete
ce2320facea8: Pull complete
898c3dbc1716: Pull complete
45c37559f24a: Pull complete
Digest: sha256:b332684117bfa05329298712ad0ffcfc4a83ce6314332e073978f46be3c05e81
Status: Downloaded newer image for ghost:4.20.3
Creating ghost_ghost_1 ... done
Attaching to ghost_ghost_1
ghost_1  | [2021-10-26 07:02:05] INFO Ghost is running in production...
ghost_1  | [2021-10-26 07:02:05] INFO Your site is now available on https://ghost.domain.com/
ghost_1  | [2021-10-26 07:02:05] INFO Ctrl+C to shut down
ghost_1  | [2021-10-26 07:02:05] INFO Ghost server started in 0.369s
ghost_1  | [2021-10-26 07:02:06] WARN Database state requires initialisation.
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: posts
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: posts_meta
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: users
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: oauth
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: posts_authors
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: roles
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: roles_users
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: permissions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: permissions_users
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: permissions_roles
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: settings
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: tags
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: posts_tags
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: invites
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: brute
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: sessions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: integrations
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: webhooks
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: api_keys
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: mobiledoc_revisions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: products
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: offers
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: benefits
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: products_benefits
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_products
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_payment_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_login_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_email_change_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_status_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_product_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_paid_subscription_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: labels
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_labels
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_stripe_customers
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_stripe_customers_subscriptions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: offer_redemptions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: members_subscribe_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: stripe_products
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: stripe_prices
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: actions
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: emails
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: email_batches
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: email_recipients
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: tokens
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: snippets
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: temp_member_analytic_events
ghost_1  | [2021-10-26 07:02:06] INFO Creating table: custom_theme_settings
ghost_1  | [2021-10-26 07:02:06] INFO Model: Product
ghost_1  | [2021-10-26 07:02:06] INFO Model: Tag
ghost_1  | [2021-10-26 07:02:06] INFO Model: Role
ghost_1  | [2021-10-26 07:02:06] INFO Model: Permission
ghost_1  | [2021-10-26 07:02:07] INFO Model: User
ghost_1  | [2021-10-26 07:02:07] INFO Model: Post
ghost_1  | [2021-10-26 07:02:08] INFO Model: Integration
ghost_1  | [2021-10-26 07:02:08] INFO Relation: Role to Permission
ghost_1  | [2021-10-26 07:02:08] INFO Relation: Post to Tag
ghost_1  | [2021-10-26 07:02:08] INFO Relation: User to Role
ghost_1  | [2021-10-26 07:02:08] INFO Database is in a ready state.
ghost_1  | [2021-10-26 07:02:08] INFO Ghost database ready in 3.309s
ghost_1  | [2021-10-26 07:02:09] INFO Ghost booted in 4.457s
ghost_1  | [2021-10-26 07:02:09] INFO Adding offloaded job to the queue
ghost_1  | [2021-10-26 07:02:09] INFO Scheduling job update-check at 49 27 22 * * *. Next run on: Tue Oct 26 2021 22:27:49 GMT+0000 (Coordinated Universal Time)
ghost_1  | [2021-10-26 07:02:51] INFO "GET /favicon.ico" 200 7ms
ghost_1  | [2021-10-26 07:02:51] INFO "GET /" 200 605ms
ghost_1  | [2021-10-26 07:02:51] INFO "GET /assets/built/screen.css?v=dde6c321bb" 200 5ms
ghost_1  | [2021-10-26 07:02:51] INFO "GET /assets/built/casper.js?v=dde6c321bb" 200 3ms
ghost_1  | [2021-10-26 07:02:52] INFO "GET /members/api/member/" 204 1ms
ghost_1  | [2021-10-26 07:02:52] INFO "GET /members/api/site/" 200 14ms
ghost_1  | [2021-10-26 07:02:52] INFO "GET /favicon.ico" 200 2ms

So you see? Without the -d option, you do launch your config, but it happens in a verbose mode without dropping back to the terminal prompt. Kinda helpful, isn't it? When you open the browser and access the ghost blog, you will find it accessible within a few moments. But what if you drop out of the console with Ctrl+Z? It will keep the process running in the background, and you can check that using the docker ps command:

avimanyu@localhost:~/ghost$ docker ps
CONTAINER ID   IMAGE                                    COMMAND                  CREATED         STATUS          PORTS                                                                      NAMES
563a45d049cf   ghost:4.20.3                             "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes    2368/tcp                                                                   ghost_ghost_1

But what happens to the container if you used Ctrl+C instead? The process straightaway gets killed.

avimanyu@localhost:~/ghost$ docker ps
CONTAINER ID   IMAGE                                    COMMAND                  CREATED         STATUS          PORTS                                                                      NAMES

When you use the -d option, what it does is very similar to Ctrl+Z: It detaches from the console and continues to run the container in the background and also prints the new container name (ghost_ghost_1 in this case).

Now, instead of using docker-compose down, let us use docker-compose stop:

avimanyu@localhost:~/ghost$ docker-compose stop
avimanyu@localhost:~/ghost$

Let's now check our running containers. Expectedly, there should be none:

avimanyu@localhost:~/ghost$ docker-compose ps
    Name                   Command               State    Ports
---------------------------------------------------------------
ghost_ghost_1   docker-entrypoint.sh node  ...   Exit 0 

Notice that I did not use docker ps. Instead, I ran docker-compose ps because I wanted to show you another way to verify the Exit 0 state. This means the container has exited/stopped.

Let us recheck this with docker ps -a. The -a flag will also look for stopped containers:

avimanyu@localhost:~/ghost$ docker ps -a
CONTAINER ID   IMAGE                                    COMMAND                  CREATED         STATUS                     PORTS                                                                      NAMES
44d09e778a91   ghost:4.20.3                             "docker-entrypoint.s…"   8 minutes ago   Exited (0) 7 minutes ago                                                                              ghost_ghost_1

What is docker-compose start then?

Docker Compose Start makes sense only when you haven't removed any container with docker-compose down (that I haven't done yet in this tutorial command line). So basically, the difference here is that it starts containers that have been stopped and not removed.

So, first, let's use start instead of up now and see what happens:

avimanyu@localhost:~/ghost$ docker-compose start
Starting ghost ... done
avimanyu@localhost:~/ghost$ 

What happens now is the stopped container is started again:

avimanyu@localhost:~/ghost$ docker-compose ps
    Name                   Command               State    Ports  
-----------------------------------------------------------------
ghost_ghost_1   docker-entrypoint.sh node  ...   Up      2368/tcp

There you go. The State is now Up back from Exit 0. You can also recheck with the docker version of the command:

avimanyu@localhost:~/ghost$ docker ps
CONTAINER ID   IMAGE                                    COMMAND                  CREATED          STATUS              PORTS                                                                      NAMES
44d09e778a91   ghost:4.20.3                             "docker-entrypoint.s…"   22 minutes ago   Up About a minute   2368/tcp                                                                   ghost_ghost_1

Let's now run the stop command again.

avimanyu@localhost:~/ghost$ docker-compose stop
Stopping ghost_ghost_1 ... done

You are aware now what the state of the container is. You can use either the up or start command to get it running back up again. Note that you can run the down in this state as well without starting the services again:

avimanyu@localhost:~/ghost$ docker-compose down
Removing ghost_ghost_1 ... done
Network net is external, skipping

The container is removed now. If there were internal networks specified inside the Docker Compose file, they would have been removed as well. Since net is external, it is skipped from removal. Had you specified the -v flag additionally, it would have been removed as well!

avimanyu@localhost:~/ghost$ docker-compose down -v
Stopping ghost_ghost_1 ... done
Removing ghost_ghost_1 ... done
Network net is external, skipping
Volume ghost is external, skipping

Always Stay Double Cautious With Your Data!

Bonus Tip: Note that since I'm using an external volume(created previously via "docker volume create volume-name"), using "-v" does not remove it. It is only meant for volumes created from your Docker Compose specifications. But on the contrary, "docker volume prune" would remove an external volume even if it is being used by a container!
Definitive Guide on Backup and Restore of Docker Containers
Harness both the cloud and your local system to backup and restore your Docker containers.

Also, you cannot use start at this stage. It works only for stopped configurations:

avimanyu@localhost:~/ghost$ docker-compose start
Starting ghost ... failed
ERROR: No containers to start

In such a case, you must use docker-compose up or docker-compose up -d once again.

avimanyu@localhost:~/ghost$ docker-compose up -d
Creating ghost_ghost_1 ... done
avimanyu@localhost:~/ghost$

Summary

I hope this article gave you a better understanding of up vs up -d vs start as well as stop vs down and down -v for Docker Compose.

A Quick Guide to Using Docker Compose
Docker Compose is a Docker-native tool that makes multi-container application management a breeze.

This expansive explainer guide should make your day-to-day docker management much easier and a lot less overwhelming from now on. Depending upon scenario to scenario, especially in production systems, which particular command you choose in order to sort out a situation, will, of course, differ in how you implement it.

If you have any thoughts, queries or suggestions to share, please leave a comment below.