Dash0 Raises $110M Series B at $1B Valuation

  • 9 min read

How to Add a Volume to an Existing Docker Container

You can't add a volume to a running Docker container, and you can't add one to a stopped container either. Mounts are set at container creation and stay fixed for the container's lifetime. Port mappings, environment variables, and the command work the same way. The docker update command can change CPU limits, memory limits, and the restart policy on a running container, but it can't touch volumes or any other mount config. This is the same immutability that trips people up when trying to change configuration with docker start.

The right approach is to recreate the container with the new mount, copying any data from the writable layer first if you want to keep it.

Recreate the container with a new volume

Before you remove anything, capture the current container's configuration so you can recreate it exactly. The same stop-remove-run pattern applies when you're updating to a new image, just with different flags. The fastest way to capture the config is docker inspect:

bash
12
docker inspect my-app \
--format '{{.Config.Image}} {{range .Config.Env}}-e {{.}} {{end}}{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}-p {{(index $conf 0).HostPort}}:{{$p}} {{end}}{{end}}'

You'll get back something like this:

text
1
nginx:latest -e PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -p 8080:80/tcp

That's your image, environment variables, and port mappings in a shape you can paste into a new docker run command. The {{if $conf}} guard skips ports that are exposed but not published, which would otherwise cause the template to error. For anything more complex, run docker inspect my-app without the format string and read through the full JSON.

Now stop and remove the existing container. If you need to preserve data that's been written to the container's writable layer (anything outside an existing volume), jump to migrating data from the old container before running docker rm:

bash
12
docker stop my-app
docker rm my-app

Then recreate it with the same arguments plus the new -v flag for a named volume:

bash
12345
docker run -d \
--name my-app \
-p 8080:80 \
-v app-data:/var/lib/app \
nginx:latest

The -v app-data:/var/lib/app part creates a named volume called app-data (if it doesn't exist yet) and mounts it at /var/lib/app inside the container. If you'd rather mount a host directory directly, use a bind mount by giving an absolute path on the left:

bash
12345
docker run -d \
--name my-app \
-p 8080:80 \
-v /srv/app/data:/var/lib/app \
nginx:latest

Either form works, but -v is overloaded: the same flag handles both named volumes and bind mounts, and you can't tell which is which by reading the command. The longer --mount syntax makes this explicit:

bash
12345
docker run -d \
--name my-app \
-p 8080:80 \
--mount type=volume,source=app-data,target=/var/lib/app \
nginx:latest

Use whichever you prefer. --mount is more verbose but unambiguous, which matters in scripts and CI pipelines that someone else will read later.

Migrating data from the old container

If the old container wrote data to a path that wasn't backed by a volume, that data lives in the container's writable layer. Removing the container deletes it. Before you run docker rm, copy the data out with docker cp:

bash
1
docker cp my-app:/var/lib/app ./app-data-backup

Now you can stop and remove the old container safely. Create the new container with the volume mounted, then copy the backed-up data into the volume using a temporary helper container:

bash
1234
docker run --rm \
-v app-data:/dst \
-v "$(pwd)/app-data-backup:/src" \
alpine sh -c 'cp -a /src/. /dst/'

The helper mounts both the named volume and your backup directory, then copies one into the other and exits. After this, your new container sees the migrated data at /var/lib/app. Once you've confirmed the application is reading the migrated data correctly, you can delete the ./app-data-backup directory on the host.

The Docker Compose workflow

If your container is managed by Compose, you don't run docker run at all. Edit compose.yaml to add the volume to the service definition:

yaml
12345678910
services:
app:
image: nginx:latest
ports:
- "8080:80"
volumes:
- app-data:/var/lib/app
volumes:
app-data:

The top-level volumes: block declares the named volume; the service's volumes: entry mounts it. Apply the change with:

bash
1
docker compose up -d

Compose detects that the service configuration changed and recreates the container. If nothing else changed about the service, this is a clean swap and Compose will pick up the new volume automatically.

If Compose doesn't recreate the container for some reason (this can happen when a volume change is technically the only diff and Compose's change detection misses it), force it:

bash
1
docker compose up -d --force-recreate

--force-recreate ignores Compose's diff check and recreates containers regardless. It's the right lever when you've made a config change that should take effect but isn't being picked up. There's more on this in how to rebuild a container in Docker Compose.

Common pitfalls

docker commit is not the answer. A widely shared Stack Overflow trick is to docker commit the running container into a new image, then docker run that image with the new volume. This works, but you end up with an opaque image that has no Dockerfile lineage, no rebuildability, and no clear record of what's actually in it. Six months later, nobody will remember why my-app:patched exists or how to recreate it. Use docker cp for data migration instead, and keep your image definitions in version control. The docker image vs container explainer covers why this matters in more depth.

Anonymous volumes get reused on recreate. If your image's Dockerfile has a VOLUME instruction (most database images do), Compose will create an anonymous volume for that path the first time the container starts. When you later recreate the container, Compose reattaches the same anonymous volume rather than creating a fresh one. If you want a clean state, the most direct fix is docker compose down -v before bringing the stack back up, which removes all volumes declared by the project. If you only want to refresh anonymous volumes and keep your named volumes intact, use docker compose up -d --renew-anon-volumes instead.

The container has to actually be gone. Running docker run with the same --name as an existing container errors out with "name already in use." This is helpful, since it stops you from accidentally orphaning the original. But it also means partial cleanups bite: if you ran docker stop and skipped docker rm, the next docker run fails. See how to remove a Docker container for the full cleanup workflow.

Final thoughts

Adding a volume is the easy part. The harder problem is knowing whether the application is using it correctly: writing the files it should, reading them back when it restarts, not running out of space. Volume issues tend to surface as application errors that look unrelated until you correlate them with disk activity.

Dash0's infrastructure monitoring tracks per-container disk I/O and filesystem activity alongside real-time logs and distributed traces, so when a recreated container starts behaving differently from the old one, you can see the change in context instead of guessing. Start a free trial to monitor your Docker workloads in one place. No credit card required.