Docker has quickly become a ubiquitous platform within software development for packaging applications into lightweight and portable containers. Central to working with Docker are the docker build
and docker run
commands – in this comprehensive guide, we‘ll understand these commands and their differences inside out.
Introducing Docker
Released in 2013, Docker is an open platform for developing, shipping, and running applications within containers. Containers encapsulate the application code along with all its software dependencies into a standardized unit that can run uniformly and reliably across environments.
Some key advantages of using Docker containers include:
- Predictable delivery from dev to production
- Lightweight, fast startup times
- Simplifies dependency management
- Environment consistency across machines
- Encourages modular microservices
- Portability across Linux and Windows
According to Datadog‘s 2022 Container Report, 89% of organizations are now running containers in production – with 74% leveraging Docker. This demonstrates the immense popularity Docker has gained over virtual machines for modern application delivery.
Docker Architecture
There are some key components that make up the Docker platform:
- Docker Engine – runtime environment for containers with Docker daemon and client
- Docker Registries – stores and distributes Docker images
- Docker Objects – images, containers, volumes, networks etc.
- Dockerfile – file with instructions for building images
The Docker daemon (dockerd) runs on the host system and handles container management like building, running and distributing images. The Docker client (docker) then communicates with dockerd to execute Docker commands.
Images vs. Containers
It‘s important to understand the difference between a Docker image and a Docker container.
- Images – The file system and configuration of the container environment.
- Containers – An instantiated running instance of an image.
Images are immutable and reusable – like a class or template. Containers are disposable instances of those images – like objects. This analogously applies the object oriented programming principles of classes and inheritance to containerization.
Docker Build Command
The docker build
command constructs a Docker image by reading instructions from a Dockerfile, which is a text file that defines how the environment is configured.
The basic syntax is:
docker build [options] PATH | URL
Where PATH points to the build context – usually the directory containing the Dockerfile.
Some options include:
- -t – Tag image with a repository name and tag
- -f – Specify Dockerfile location
For example:
docker build -t myimage:1.0 .
This builds an image named myimage:1.0
from the Dockerfile in the current directory.
Dockerfile Instructions
A Dockerfile defines a sequence of instructions for assembling an image. Here are some common examples:
- FROM – The base image to start building on top of
- RUN – Execute shell commands
- COPY – Copy files from the build context into the image
- CMD – Defines the default command to run on container startup
- EXPOSE – Expose ports that the container will listen on
A simple Node.js Dockerfile:
FROM node:16-alpine
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
When docker build
runs, it sends the context directory to the Docker daemon and executes these steps sequentially – resulting in the final image.
Each step creates a container, runs commands, and commits the container state as a new image layer. This means intermediate images are cached, optimizing subsequent builds.
Multi-Stage Builds
A powerful Dockerfile technique is multi-stage builds – using multiple FROM statements to keep build tools separate from the production image.
For example:
# Builder
FROM node as build
WORKDIR /app
COPY . .
RUN npm install && npm run build
# Production
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
This builds the Node app in the first stage, then copies the production output into an optimized Nginx container for deployment.
Docker Run Command
While docker build
generates images, the docker run
command instantiates containers from those images. Think of it as executing the image.
The syntax is:
docker run [options] IMAGE [command] [args]
This runs a container from IMAGE
, executing command
within it.
Some useful options:
- -d – Detached/background mode
- -p – Publish container ports to host
- –name – Name the running container
For example:
docker run -dp 3000:3000 --name mycontainer myimage
This runs myimage
in detached mode, naming it mycontainer
and publishing port 3000 to the host.
We can then interact with the running container – inspect logs, execute commands or connect via the network ports.
Container Lifecycle
Containers have a standard lifecycle:
- Running – The active state when a container is executing
- Paused – Freezing the container process without cleaning up resources
- Restarting – Stopping then starting a container
- Stopped – Manually halt execution of a container
- Deleted – Removing a stopped container and freeing resources
By default, containers are ephemeral and stateless – storing data outside the container using volumes. This facilitates replacing containers without losing data.
Key Differences Between Docker Build and Run
Understanding the distinct purposes and outcomes of the Docker build and run commands is critical for effectively working with Docker.
docker build | docker run | |
---|---|---|
Purpose | Assembles a Docker image from a Dockerfile | Launches a container from an image |
Persistent? | Yes, images remain stored | No, containers are ephemeral by default |
Frequency | Occurs only when needed to update image | Can be executed repeatedly to spawn containers |
Output | Docker Image (template) | Docker Container (running instance) |
Process | Usually done during development/CI | Done during deployment/production |
In summary:
- “docker build“ – Builds reusable images from Dockerfiles
- “docker run“ – Launches disposable containers from images
So docker run
leverages the artifacts produced by docker build
.
Implementing a Full Workflow
Let‘s go through a sample workflow to see these commands in action.
1. Write Dockerfile
First, we write a simple Dockerfile for a Node.js application:
FROM node:16-alpine
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "app.js"]
This base image is Node 16 Alpine, copies the context, installs npm dependencies and runs the app.
2. Build Image
Next, we can build the image using docker build
:
$ docker build -t myapp:latest .
[+] Building 0.2s
...
=> exporting to image
=> => naming to docker.io/library/myapp:latest
This generates an image named myapp:latest
containing our application from the Dockerfile.
We can now view the image locally using docker images
:
$ docker imagesmyapp latest cddeb2d43b3d About a minute ago 91MB
3. Run Container
With our image built, we can start a containerized instance of it using docker run
:
$ docker run -dp 3000:3000 myapp:latest
abcd0123333e32221b3f02b12e4ca40433ef2f5989e12313...
This launches a container exposing port 3000, using the latest myapp
image.
We can verify the container is running with docker ps
:
$ docker psCONTAINER ID IMAGE PORTS
abcd01233 myapp:latest 0.0.0.0:3000->3000/tcp
And interact via the network port or container commands.
This demonstrates the end-to-end workflow leveraging Docker build and run.
Conclusion
Docker adoption continues accelerating for modern application delivery thanks to the portability, consistency and deployment velocity of containers. Mastering the core docker build
and docker run
commands is essential for developers looking to build, ship and operate containerized workloads effectively.