Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › containers

Docker Development Workflow: Turn Dockerfiles Into Running Services

By Freedom251 Team · · 10 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Short answer: a Dockerfile is the recipe for building an image, an image is the immutable, layered artifact produced by that build, a container is a running instance of an image with its own temporary writable layer, and Docker Compose is a YAML-defined runtime model for starting several services together. Keeping those four roles separate prevents the most common mistakes with builds, local development, networking and data loss.

Docker’s own distinction is precise: “A Dockerfile provides instructions to build a container image while a Compose file defines your running containers.” The Dockerfile answers what should be inside the artifact; Compose answers which services should run, with what settings, and how they connect.

As an Amazon Associate I earn from qualifying purchases.

The four Docker concepts, in the order they fit together

1. Dockerfile: build-time instructions

A Dockerfile is a plain-text file containing ordered build instructions. It normally starts with FROM, which selects a base image, then uses instructions such as RUN to execute commands while building, WORKDIR to set the directory for later instructions, COPY to add project files, and CMD to provide the default program when a container starts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For example:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

This file does not start your application and does not create a database. It describes how to assemble an image. Build it from the directory containing the file with:

docker build -t example-app:dev .

The final period is the build context: Docker can copy files from that directory (subject to any .dockerignore rules), but not from arbitrary paths outside it.

2. Image: the built, layered artifact

The build produces an image made of read-only layers. Each relevant Dockerfile instruction can contribute a layer, and unchanged layers can be reused from cache on a later build. The image contains your application files, dependencies, metadata and default startup configuration, but it is not itself a running process.

Tagging gives the image a human-readable reference such as example-app:dev. A registry can store and distribute the same image, while a digest identifies an exact immutable content version. Treat tags such as latest as mutable labels rather than permanent version numbers; use a project or release tag when reproducibility matters.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Container: a runtime instance

A container is created from an image. Docker adds a writable layer above the image’s read-only layers, starts the configured process and attaches the requested network, environment variables, ports and mounts. Two containers can run from the same image while having different names, settings or data.

Writing a file inside a container changes that container’s writable layer, not the underlying image. If the container is removed, data stored only in that layer is removed with it. Stopping a container preserves the layer; destroying it does not. A container is therefore an isolated runtime, not a durable storage plan.

4. Compose: runtime configuration for services

Compose uses a YAML file, commonly named compose.yaml, and the docker compose CLI. It declares services, the images they use or the source they build, ports, environment, mounts, dependencies and networks. A service represents an application component that can be run once or scaled to multiple equivalent containers.

Compose does not replace a Dockerfile. A Dockerfile builds one image; a Compose file can run that image alongside a database, cache, worker or other components.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build one application image with a Dockerfile

Choose a base and order instructions for useful caching

FROM establishes the starting filesystem and toolchain. Use a base appropriate to the language and deployment target, and pin a deliberate version rather than relying on an unexamined moving tag. Put infrequently changing dependency files before frequently changing source files. In the Python example, copying requirements.txt and installing dependencies before copying the rest of the source allows Docker to reuse the dependency layer when only application code changes.

Understand build-time versus runtime settings

RUN executes during docker build. It is the right place to install packages, compile assets or create files that belong in the image. CMD is a default runtime command and is evaluated when a container starts; it is not executed during the build. Runtime values such as a database hostname, port or secret should normally be supplied by Compose, an environment variable, a secret mechanism or the deployment platform, rather than baked into the image.

Use a small, intentional build context

A .dockerignore file can exclude virtual environments, dependency caches, local databases, test output and secret files from the build context. Excluding unnecessary content speeds builds and reduces the chance of copying credentials into an image. Never assume that deleting a file in a later layer erases it from image history; avoid adding secrets in the first place.

Inspect and test the result

After building, list local images with docker image ls. Start a disposable container:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker run --rm -p 8000:8000 example-app:dev

The --rm option removes that container when its main process exits. Use docker ps for running containers, docker ps -a for stopped ones and docker logs <container> to inspect standard output and error.

What actually changes when a container runs

Process and filesystem isolation

Docker starts the image’s configured command as the container’s main process. The container receives an isolated filesystem view, process namespace and network configuration, subject to the host operating system and Docker runtime. If that main process exits, the container stops; a stopped container can be started again, but it does not become a new image.

Image immutability and the writable layer

Suppose an application writes /app/output.json without a mount. The file is placed in the container’s writable layer. A second container created from the same image will not see it, and removing the first container removes the file. Changes made in one container never mutate the image shared by other containers.

Ports are published, not automatically exposed

A process listening on a container port is reachable by other containers on an attached Docker network, but it is not necessarily reachable from the host. Publishing -p 8000:8000 maps host port 8000 to container port 8000. In Compose, the equivalent is a ports entry. Avoid publishing internal database or cache ports unless a host-side client genuinely needs them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Compose a multi-service application

A minimal web-and-Redis example

The following file builds the web service from the local Dockerfile and obtains Redis from an existing image:

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      REDIS_HOST: redis
    depends_on:
      - redis
  redis:
    image: redis:7
    volumes:
      - redis-data:/data

volumes:
  redis-data:

web has a build section, so Compose runs a build using the local context. redis has an image field, so Compose starts a container from that image instead of building it from project source. Compose creates a network for the project; the web service can reach Redis at the hostname redis, the service name. You normally should not use localhost for that connection: inside the web container, localhost refers to the web container itself.

depends_on expresses startup order, not application readiness. Redis may have a container running before it is ready to accept connections. Applications should retry connections, or you should add an appropriate health check and dependency condition for the Compose implementation and version you use.

Start, rebuild and inspect the stack

  1. From the directory containing compose.yaml, run docker compose up to create and start the declared services.
  2. Use docker compose up --build when a service’s Dockerfile or build context changed and you want Compose to build before starting.
  3. Add -d to run in the background: docker compose up -d.
  4. Stream service output with docker compose logs -f web, and check state with docker compose ps.
  5. Stop the application with docker compose down. This removes the project’s containers and network; named volumes are retained unless you explicitly add -v.

docker compose up versus docker compose run

Command Purpose Typical result
docker compose up Run the application model defined in the Compose file. Creates and starts the project’s services, networks and configured mounts; it can keep running in the foreground or detach.
docker compose up --build Rebuild images before starting the model. Useful after Dockerfile or source changes that affect a build.
docker compose run web <command> Run a one-off command for one service. Creates a temporary container using the web service configuration and starts its declared dependencies when needed; suitable for tests, migrations or shells.

For example, run a test suite without replacing the normal web process with docker compose run --rm web pytest. The --rm flag removes the one-off container afterward. Use up for the long-lived application stack and run when the task is an ad-hoc command attached to one service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where data lives: writable layers, volumes, bind mounts and tmpfs

Choose storage by asking who must access the files, how long they must survive and whether the data should ever reach disk.

Storage type Lifecycle Management and host visibility Good developer use
Container writable layer Belongs to one container; removed when that container is destroyed. Managed internally by Docker; not a dependable host-shared data location. Temporary scratch files or disposable test output.
Named volume Persists beyond an individual container and can be attached to replacement containers. Managed by Docker; host access is indirect and implementation-specific. Database state or other container-generated data that must survive recreation.
Bind mount Follows the host path; data remains when containers are replaced. You choose the host directory and both host and container can access it. Mounting source code during development so edits appear immediately in the container.
tmpfs mount Exists only for the current container session and disappears afterward. Stored in host memory rather than persistent storage. Ephemeral data that should not persist to disk.

Docker recommends volumes for data generated by and used by containers. Bind mounts are appropriate when the host and container must work on the same files. A named volume for database data and a bind mount for editable application source is a common local-development combination, but neither is universal: select the mount based on persistence, host access and security requirements.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A repeatable development workflow

  1. Define the build. Write a Dockerfile that installs runtime dependencies and sets a sensible default command. Add a focused .dockerignore.
  2. Build and label. Run docker build -t project-web:dev . and verify the image starts on its own when practical.
  3. Model services. Add a Compose file with one service per application component. Use build for code you own and image for an image you consume.
  4. Configure runtime values. Put ports, environment variables, service names, dependencies and mounts in Compose. Keep secrets out of the image and source-controlled YAML.
  5. Start and observe. Run docker compose up --build, then inspect logs and status. A container that is “running” can still have an unhealthy application, so read the service logs.
  6. Run one-off tasks. Use docker compose run --rm web followed by a test, migration or shell command. Do not alter the long-running command merely to perform a single task.
  7. Recreate intentionally. After changing a service image or configuration, rebuild and recreate the affected service. Do not expect an already-created container to acquire a new image automatically.

Compose beyond a laptop

Compose can describe environments used in development, CI, staging and some production deployments. Docker documents using a production-specific Compose file to change settings such as source-code mounts, host-port exposure, environment values, restart policy or additional services. A common pattern is to keep a base file for shared service definitions and apply a production override that removes development bind mounts, selects production images and sets deployment-specific values.

When application code changes, rebuild the affected image and recreate the service so the running container actually uses the new artifact. Compose is a configuration and lifecycle tool; it is not automatically the right orchestrator for every production system. Decisions about clustering, scheduling, failover, secrets, observability and rollout strategy still depend on the scale and reliability requirements of the deployment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failure modes and fixes

“My changes are not visible”

If source code was copied into an image during docker build, editing the host file does not alter an existing container. Rebuild with docker compose up --build, or bind-mount the source directory for development. Be careful that a bind mount can hide files that were present at the same path in the image.

“The database emptied after I recreated containers”

Database files stored only in the writable layer are expected to disappear when the container is destroyed. Attach a named volume at the database’s data directory, confirm it appears in docker volume ls, and make backups; a volume protects against container replacement but is not a backup strategy by itself.

“The web service cannot connect to Redis”

Use the Compose service name, such as redis, as the hostname and the container’s listening port, not a host-published port. Check docker compose ps and service logs. If the connection fails only during startup, add client retries or a readiness-aware health-check arrangement.

“A port is already allocated”

Another process or container is using the host port in the Compose ports mapping. Stop the conflicting process, remove the old container, or choose another host port such as "8001:8000"; the number on the right remains the port inside the container.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“The image is unexpectedly large or rebuilds are slow”

Reduce the build context with .dockerignore, order stable dependency installation before frequently changing source copies, remove package caches where appropriate, and use a smaller base image only after checking compatibility. Layer caching improves build time but does not replace security updates or deliberate dependency pinning.

A compact decision guide

  • Need to describe how software is assembled? Write a Dockerfile.
  • Need the reusable artifact produced by that recipe? Use an image.
  • Need to start and isolate a process from that artifact? Create a container.
  • Need an application made of a web service, worker, database and cache with shared configuration? Use Compose.
  • Need data to survive container destruction? Use a volume or an appropriate bind mount, not the writable layer alone.
  • Need a temporary test, migration or shell command? Use docker compose run; need the normal stack? Use docker compose up.

Optional deeper reading is Docker Deep Dive, Fifth Edition, which covers images, containers and volumes. Check the publisher’s current edition and availability before purchasing.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

containersDeveloper ToolsDockerDocker ComposeDocker imagesDockerfilevolumes

Leave a Reply

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

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.