There are two reasons people install software on their dev machine: they need it long-term, or they need it for the next 30 minutes. The second case is almost always better served by running it in Docker — try the thing, prove it does what you want, and walk away with a clean machine when you're done.
This is the hub article for that pattern. The shape is one docker run command, optionally with a persistent volume for state, that you can copy-paste, replace with your version of choice, and run. No package manager, no PATH, no leftovers in /usr/local. The article links to the specific per-tool recipes for the cases where you want more (a persistent database, a configured web server, a built Dockerfile).
How do I run software without installing it on my computer?
docker run --rm -it IMAGE_NAME [COMMAND]That's the whole pattern. --rm deletes the container the moment it exits so nothing is left behind. -it gives you an interactive terminal if the tool needs one. IMAGE_NAME is whatever you want to run — an official image from Docker Hub usually exists. For a one-shot CLI tool that produces output and quits, drop the -it:
docker run --rm IMAGE_NAME commandThe rest of this page is the variations: how to mount your current directory so the tool can see your files, how to expose a port so your browser can reach a server you started in a container, how to keep data around between runs (databases), and how to actually use this for the most-searched cases.
Jump to:
- The basic pattern
- Bind-mounting your working directory
- Exposing a port (for servers)
- Persistence (for databases)
- Running as your user, not root
- Per-tool recipes
- Common pitfalls
- FAQ
The basic pattern
A throwaway shell inside an Ubuntu container:
docker run --rm -it ubuntu:24.04 bashWhen you exit, the container is gone. Try cat /etc/os-release, install something with apt, write a file in /tmp — none of it persists. The next docker run is a fresh container.
Same shape for a quick Python REPL on a specific version without touching system Python:
docker run --rm -it python:3.13 pythonOr a quick jq invocation on JSON without installing jq:
echo '{"a": 1}' | docker run --rm -i ghcr.io/jqlang/jq '.a'The pattern is docker run --rm [flags] IMAGE [command]. Everything else in this article is the flags that matter for specific cases.
Bind-mounting your working directory
Most tools need to see your files. Mount the current directory and set it as the working directory:
docker run --rm -v "$(pwd):/work" -w /work IMAGE command-v "$(pwd):/work" maps the current host directory to /work inside the container. -w /work sets /work as the container's working directory, so the command runs against your files.
Examples that come up constantly:
# Format JSON files in place with jq
docker run --rm -v "$(pwd):/work" -w /work ghcr.io/jqlang/jq -i '.' file.json
# Run npm install for a Node project without Node installed
docker run --rm -v "$(pwd):/work" -w /work node:22 npm install
# Run pytest against a Python project
docker run --rm -v "$(pwd):/work" -w /work python:3.13 sh -c 'pip install -r requirements.txt && pytest'
# Run terraform plan
docker run --rm -v "$(pwd):/work" -w /work hashicorp/terraform planTwo things to watch:
- File ownership. By default the container runs as root, so any files it creates on the bind mount are owned by root on your host. Add
-u $(id -u):$(id -g)to run as your user. - Mac / Windows performance. Bind mounts in Docker Desktop go through a virtualized filesystem; large directories can feel slow. For dev workflows that touch thousands of files, named volumes (or moving the project into your WSL2 filesystem on Windows) help. See Why Bind Mounts Are Slow on Mac and Windows.
Exposing a port (for servers)
When the thing you're running is a server (database, web server, API), publish its port:
docker run --rm -p HOST_PORT:CONTAINER_PORT IMAGEExamples:
# Nginx on http://localhost:8080
docker run --rm -p 8080:80 nginx:alpine
# PostgreSQL listening on localhost:5432, no persistence (dies when you stop it)
docker run --rm -e POSTGRES_PASSWORD=hello -p 5432:5432 postgres:16
# Redis on localhost:6379
docker run --rm -p 6379:6379 redis:alpineContainer port on the right, host port on the left. The container always listens on its own port; the host port is what your browser or local app uses to reach it.
For databases, this throwaway form is fine for quick tests but loses everything on exit. That's the next section.
Persistence (for databases)
The instant a container exits with --rm, every byte in its writable layer is gone. For databases, that's everything you've created. The fix is a named volume:
docker run -d --name CONTAINER_NAME \
-p HOST_PORT:CONTAINER_PORT \
-v VOLUME_NAME:/data/path/inside/container \
IMAGEThree changes from the throwaway form:
-dinstead of--rm— runs detached, so the container survives the shell.--name— gives you a stable handle fordocker stop,docker start,docker logs.-v NAME:/path— a named volume that Docker manages, persistent across container removal.
Walk through with Postgres:
# First run — creates the volume, initializes the database
docker run -d --name pg \
-p 5432:5432 \
-e POSTGRES_PASSWORD=hello \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# Use it. Connect with psql, run queries, create tables.
# Stop and remove the container
docker stop pg
docker rm pg
# Start a NEW Postgres container against the SAME volume
docker run -d --name pg \
-p 5432:5432 \
-e POSTGRES_PASSWORD=hello \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# All your tables are still there.The named volume pgdata survives container removal. Deletes only happen when you explicitly docker volume rm pgdata or docker volume prune it.
The same shape works for every persistent service — the only thing that changes is which path inside the container is the "data dir":
- MySQL / MariaDB:
/var/lib/mysql - PostgreSQL:
/var/lib/postgresql/data - MongoDB:
/data/db - Redis:
/data(plus--appendonly yesto actually enable persistence) - Elasticsearch:
/usr/share/elasticsearch/data - MinIO:
/data
Each per-tool recipe below has the right path baked in.
Running as your user, not root
When you bind-mount your project and the container writes files, those files are owned by whatever UID the container ran as — root by default. That leaves root-owned files on your host. Annoying, occasionally a real problem.
# Run as your UID and GID
docker run --rm -u "$(id -u):$(id -g)" -v "$(pwd):/work" -w /work IMAGE commandSome images don't tolerate arbitrary UIDs (Postgres official image is one), so this trick is best for build / processing / CLI containers, not databases. For databases, set permissions on the host directory before mounting, or use a named volume so Docker manages ownership.
Per-tool recipes
Each of these links to a full article with the working command, the persistence section, a practical-usage example, and the common pitfalls for that specific tool.
Databases:
- Run MySQL in Docker
- Run MariaDB in Docker
- Run PostgreSQL in Docker
- Run MongoDB in Docker
- Run Redis in Docker (with AOF + RDB persistence)
- Run Elasticsearch in Docker
Web servers and apps:
Language runtimes:
Operating systems and shells:
Dev tooling:
- Run MinIO in Docker (Local S3-Compatible Storage)
- Run Mailpit in Docker (Local Email Testing)
- Run Adminer in Docker (Database GUI Without Installing One)
Common pitfalls
- Forgetting
--rmand ending up with a graveyard. A week laterdocker ps -ais a long list of stopped one-shot containers. Either add--rmfrom the start ordocker container pruneperiodically. - Skipping the volume on a database. First
docker rmremoves the container, all your data goes with it. Always name a volume for stateful services. - Bind-mounting and getting root-owned files on the host. Add
-u "$(id -u):$(id -g)"for build / processing containers, or accept that you'll occasionally needsudo chownto clean up. - Port conflicts. "Port is already in use" because something else (a previous container, a host process, another tool) is bound to the same port.
docker psto find another container;lsof -i :PORTfor host processes. See Port is Already Allocated. - Apple Silicon and amd64-only images. Some vendor images don't ship arm64 builds. They run under emulation (slower) or fail with "exec format error." See exec format error — Apple Silicon and Multi-Arch Docker Images.
FAQ
Sources
Authoritative references this article was fact-checked against.
- docker run — official CLI referencedocs.docker.com
- Docker Hub (official image registry)hub.docker.com

