Running containers: ports, volumes and Docker Compose
Publish ports, keep data with volumes, connect services in Compose, and push images to a registry.
Once you have an image, you need to run it so people can reach it and so its data survives. Most beginner problems with Docker come from three areas covered here: ports, storage and how containers find each other.
Publishing ports
A container has its own network. To reach it from your machine, publish a port with -p host:container:
docker run -d --name web -p 8080:80 nginx:1.27
Here nginx listens on port 80 inside the container, and you open port 8080 on the host. The common mistake is reversing the two numbers. Useful commands while running: docker ps (running containers), docker ps -a (including stopped), docker logs web and docker exec -it web sh to open a shell inside.
Keeping data with volumes
Anything written inside a container goes to its writable layer and is deleted with docker rm. For data that must survive, mount storage:
- Named volume:
-v pgdata:/var/lib/postgresql/data. Docker manages where it is stored. Best for databases. - Bind mount:
-v ./src:/app/src. Links a host folder into the container. Useful in development so code edits appear without rebuilding.
Docker Compose
Compose describes several containers in one file and starts them together:
services:
web:
build: .
ports:
- "3000:3000"
environment:
DB_HOST: db
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Compose creates a shared network, so the web service reaches the database at host name db, not localhost. Inside a container, localhost means that container itself. Note that depends_on controls start order but does not wait for the database to be ready, so the app should retry its connection. Use docker compose up -d to start, docker compose logs to read output and docker compose down to stop and remove containers. Named volumes are kept unless you add -v.
Registries and tags
A registry stores images so other machines can pull them. Tag the image with the registry and repository name, then push:
docker tag myapp registry.example.com/team/myapp:1.4.0
docker push registry.example.com/team/myapp:1.4.0
Use explicit version tags for deployments, because latest is only a default name.
Checklist: correct port order, a named volume for any database, service names as host names, and a version tag on every image you deploy.
More in Cloud and DevOps