Docker Compose Run Command

Written by

in

Docker Compose Run Command: A Complete Practical Guide

Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

Running a one-off container without touching your main service stack is a common need during development, debugging, and CI pipelines. The docker compose run command exists exactly for this — it lets you spin up a temporary container from a service definition, execute a command inside it, and clean up afterward. This guide explains how the docker compose run command works, how it differs from docker compose up, and how to use it safely in real projects.

What the Docker Compose Run Command Actually Does

The docker compose run command starts a new container based on a service defined in your docker-compose.yml, but it does so outside the normal lifecycle of docker compose up. Instead of starting every service and keeping them running in the background, it starts exactly one service, runs the command you specify (or the image’s default command), and then exits when that command finishes.

This makes it fundamentally different from docker compose up, which is designed to bring your entire application stack online and keep it running. The docker compose run command is closer in spirit to docker run, except it inherits the network, volumes, and environment configuration already defined for the service in your compose file.

A typical use case looks like this:

docker compose run web python manage.py migrate

Here, web is the service name from docker-compose.yml, and everything after it is the command to execute inside a new container built from that service’s image.

Why Use It Instead of docker compose up

There are a few concrete reasons developers reach for the docker compose run command rather than starting the full stack:

  • You want to run a database migration, seed script, or one-time setup task.
  • You need an interactive shell inside a container to inspect the filesystem or debug an issue.
  • You’re running a test suite that shouldn’t leave a long-lived container behind.
  • You want to try a command against a service’s image without affecting containers that are already running.
  • Because it creates a new, isolated container each time, the docker compose run command won’t interfere with services you already have running via docker compose up -d.

    How It Differs from docker compose exec

    It’s worth distinguishing docker compose run from docker compose exec, since both are commonly confused:

  • docker compose exec runs a command inside an already-running container.
  • docker compose run creates a brand-new container from the service definition and runs the command there.
  • If your web container is already up and you just want to open a shell in it, docker compose exec web bash is the right tool. If the container isn’t running yet, or you specifically want an isolated, disposable instance, the docker compose run command is the correct choice.

    Basic Syntax and Common Flags

    The general form of the docker compose run command is:

    docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]

    SERVICE must match a service name defined under services: in your compose file. COMMAND and ARGS are optional — if omitted, the container runs whatever CMD or ENTRYPOINT the image defines.

    Some of the most useful options:

  • --rm — automatically removes the container once the command exits (highly recommended for one-off tasks).
  • -it — allocates an interactive TTY, useful for shells (this is actually the default in most terminals, but useful to know explicitly).
  • -e KEY=VALUE — sets an environment variable for that run only.
  • --no-deps — skips starting linked services (by default, docker compose run will start dependent services defined via depends_on).
  • --entrypoint — overrides the image’s entrypoint for this run.
  • -p — publishes a port, since docker compose run does not publish ports defined in the compose file by default.
  • A Practical Example

    Suppose you have this service defined:

    services:
      web:
        build: .
        volumes:
          - .:/app
        environment:
          - DEBUG=true
        depends_on:
          - db
      db:
        image: postgres:16
        environment:
          - POSTGRES_PASSWORD=example

    To run a database migration without leaving a stray container behind:

    docker compose run --rm web python manage.py migrate

    This starts db (because of depends_on), starts a new web container, runs the migration, and removes the container afterward while leaving db running for any subsequent runs.

    Skipping Dependencies with --no-deps

    If db is already running from an earlier docker compose up, you don’t need Compose to start it again. In that case:

    docker compose run --no-deps --rm web python manage.py migrate

    This tells the docker compose run command to skip starting linked services and just run against whatever containers are already up.

    Passing Environment Variables and Overriding Commands

    One of the more practical features of the docker compose run command is the ability to override environment variables or the container’s default command without editing your compose file.

    docker compose run --rm -e DEBUG=false web python manage.py check

    You can also override the entrypoint entirely, which is handy when debugging an image whose default entrypoint launches an application server rather than a shell:

    docker compose run --rm --entrypoint sh web

    This drops you into a shell inside a container built from the web service’s image, bypassing whatever the image would normally execute — useful for inspecting installed packages, checking file permissions, or verifying that environment variables are set correctly. If you’re working through configuration questions like this, it’s worth comparing with how Docker Compose Env variables are structured, since misconfigured environment variables are one of the most common reasons a one-off run behaves differently than expected.

    Running Interactive Shells for Debugging

    A very common pattern is opening an interactive shell against a service to debug something without affecting the running stack:

    docker compose run --rm web bash

    If bash isn’t available in a minimal image (such as an Alpine-based one), fall back to sh:

    docker compose run --rm web sh

    From inside that shell you can check installed dependencies, inspect mounted volumes, or manually run the command that’s failing in your actual container to see the full error output.

    Networking and Port Behavior

    By default, the docker compose run command does not publish the ports defined under a service’s ports: section. This trips people up regularly — they expect to reach a web server they just started with docker compose run, but nothing responds on the expected port.

    If you genuinely need a port exposed for a one-off run, use --service-ports to publish the ports as defined in the compose file:

    docker compose run --rm --service-ports web

    Or publish a specific port manually:

    docker compose run --rm -p 8000:8000 web

    Networking-wise, the container created by docker compose run joins the same Compose-managed network as your other services, so it can still reach a database or cache container by service name (e.g. db, redis) even without ports being published to the host.

    Working with Databases During a Run

    Since the new container shares the project’s network, you can connect to a database service directly by its service name from inside a run:

    docker compose run --rm web psql -h db -U postgres

    This is a common pattern when debugging data issues — you get a disposable container with your application’s environment and dependencies, but you can still poke at the real database service. For a full walkthrough of setting up a database service in Compose, see the guide on Postgres Docker Compose setup.

    Cleaning Up After docker compose run

    Without --rm, containers created by the docker compose run command are left behind after they exit — Compose doesn’t remove them automatically the way it removes containers on docker compose down. Over time, running many one-off commands without --rm can accumulate a pile of stopped containers.

    Best practice for one-off invocations:

    docker compose run --rm SERVICE COMMAND

    If you forget --rm and end up with leftover containers, you can list and remove them:

    docker ps -a --filter "label=com.docker.compose.project=yourproject"
    docker container prune

    docker container prune removes all stopped containers system-wide, so use it carefully on shared hosts where other stopped containers might be intentional.

    Comparing Cleanup Behavior with docker compose down

    It’s worth being clear that docker compose run --rm only cleans up the one-off container it creates — it has no effect on your regular service containers. If you actually want to stop and remove your whole stack, that’s a separate operation covered in detail in Docker Compose Down: Full Guide to Stopping Stacks. The two commands solve different problems: run is about executing a single task, down is about tearing down the entire application.

    Troubleshooting Common Issues

    A few issues come up repeatedly when people first start using the docker compose run command:

  • Service not found: double-check the service name matches exactly what’s under services: in your compose file — not the container name, not the image name.
  • Port not reachable: remember that ports aren’t published by default; add --service-ports or -p.
  • Unexpected dependent containers starting: use --no-deps if you don’t want depends_on services started automatically.
  • Container immediately exits with no output: check that the command you passed actually exists inside the image, and consider running with --entrypoint sh to inspect the container manually.
  • Build not picked up: if you changed your Dockerfile, run docker compose build (or docker compose up --build) before your next docker compose run, since run uses whatever image was last built. For build-related caching issues specifically, see Docker Compose Rebuild.
  • If logs from a related running service would help you understand what’s going wrong, docker compose logs is the complementary command for that — a full breakdown is available in Docker Compose Logs: The Complete Debugging Guide.

    Verifying What Actually Ran

    If a docker compose run invocation behaves unexpectedly, it can help to confirm exactly which image and configuration were used. docker inspect on the resulting container (before it’s removed, so skip --rm temporarily) will show you the exact environment variables, mounted volumes, and command that were applied — useful for catching cases where a stale image or an unexpected environment override caused the behavior you didn’t expect.

    Where This Fits in a Larger Deployment

    If you’re managing infrastructure where these commands run — for example a small VPS hosting a Compose-based application stack — it helps to have a reliable, reasonably priced host to run this on. Providers like DigitalOcean or Hetzner are commonly used for exactly this kind of Docker Compose workload, since they offer predictable pricing and straightforward SSH access for running commands like docker compose run directly on the box.

    For teams comparing Compose against more complex orchestration, it’s also worth understanding when you’d want to move beyond a single-host Compose setup entirely — see Kubernetes vs Docker Compose: Which Should You Use? for that comparison. And if you’re unsure whether you even need a separate Dockerfile alongside your compose configuration, Dockerfile vs Docker Compose: Key Differences Explained covers that distinction directly.

    For the official and most authoritative reference on all available flags and behavior, consult the Docker Compose CLI reference and the broader Docker documentation. Since Compose file syntax itself evolves, it’s also worth checking the Compose specification maintained alongside Docker’s official docs for how service definitions are structured.

    Conclusion

    The docker compose run command is a precise tool for a specific job: running a single, disposable command against a service definition without starting or disturbing your full application stack. It’s the right choice for migrations, one-off scripts, debugging shells, and test runs — while docker compose up remains the tool for actually running your application, and docker compose exec is for reaching into containers that are already running. Understanding the differences in networking, port publishing, and cleanup behavior between these commands will save you from a surprising number of “why isn’t this working” debugging sessions. Once these patterns are familiar, the docker compose run command becomes one of the more frequently used tools in a day-to-day Compose workflow.


    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Does docker compose run start dependent services automatically?
    Yes, by default it starts any services listed under depends_on for the service you’re running. Use --no-deps if you want to skip that and only run against services that are already up.

    Why can’t I reach the port from my docker compose run container?
    Because docker compose run does not publish ports from the compose file by default. Add --service-ports to publish all defined ports, or -p host:container to publish a specific one.

    Does docker compose run remove the container when it’s done?
    No, not unless you pass --rm. Without it, the container remains stopped on disk and needs to be manually removed or cleaned up with docker container prune.

    What’s the difference between docker compose run and docker compose exec?
    docker compose run creates a brand-new container from a service definition and runs a command in it. docker compose exec runs a command inside a container that is already running. Use run for one-off or disposable tasks, and exec when you need to interact with a live service.

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *