Containers, Local Development, and Deployment Artifacts
LESSON
Containers, Local Development, and Deployment Artifacts
By the end of this lesson, you will be able to...
Explain what a container image preserves about a backend runtime.
Distinguish a local development stack from the deployable application artifact.
Review an image, entrypoint, and readiness check for common deployment failures.
Idea in one sentence: A container image packages the backend runtime and startup command, while configuration, secrets, external services, and readiness evidence stay at runtime boundaries.
Core Insight
The previous lesson separated code from deploy-time configuration.
Now imagine the orders API has correct code and valid production configuration. CI passes. The release deploys.
Then the service crashes on startup:
error: libpq.so.5: cannot open shared object file
The database driver needs a native PostgreSQL library. That library exists on developer laptops. It exists in one CI job. It does not exist inside the production image.
The code did not change. The configuration did not point at the wrong environment. The runtime package was incomplete.
This is the problem containers are meant to narrow.
A backend is not only source code. It depends on:
- a language runtime,
- operating-system packages,
- installed dependencies,
- compiled native libraries,
- filesystem paths,
- startup commands,
- exposed ports,
- shutdown behavior,
- configuration injected at runtime.
The naive idea is:
If it runs on my machine, deployment should be fine.
Containers improve that story by packaging more of the runtime into an artifact. But they do not make production automatic. Secrets should not be baked into the image. Databases usually live outside the image. Readiness checks still need to say whether the process can serve.
The better model is:
image -> packaged runtime artifact
container -> running instance of that artifact
runtime config -> values injected by the environment
dependency services -> external systems the app talks to
readiness -> evidence that this instance should receive traffic
The Small Situation
Use one service: orders-api.
It talks to:
PostgreSQL
orders queue
payment provider
Developers run a local stack:
orders-api
postgres
redis
fake-payment-provider
Production deploys an application image:
orders-api:<commit-sha>
The visible pieces are:
- Source code: handlers, migrations, tests, and configuration loader.
- Build context: the files available to the image build.
- Build recipe: Dockerfile or other instructions that create the image.
- Image: the packaged filesystem and metadata.
- Container: a running instance of the image.
- Entrypoint: the command the platform starts.
- Runtime configuration: environment variables and secret references injected when the container runs.
- Dependency services: database, queue, cache, provider API, or object store outside the app image.
- Readiness check: evidence that the instance should receive traffic.
- Registry and platform: where the image is stored and where containers run.
Plain meaning:
An image is the thing you build and move around.
In this scenario:
orders-api:abc123 contains the application runtime, installed dependencies, application files, and startup metadata.
Technical name:
That is a container image.
Plain meaning:
A container is the image actually running as a process with runtime settings.
In this scenario:
The platform starts orders-api:abc123, injects DATABASE_URL, and gives it network access.
Technical name:
That is a running container.
A Build-to-Run Trace
Trace one release:
Input:
git commit abc123
build recipe
dependency lock files
Step 1: CI builds image
base runtime image is selected
OS packages are installed
language dependencies are installed
application files are copied
build or compile step runs
entrypoint metadata is set
Intermediate state:
image orders-api:abc123 exists
Step 2: tests check the artifact or equivalent output
unit tests run
integration tests run with test services
startup config validation can be exercised
Step 3: image is pushed to registry
tag or digest identifies the artifact
Step 4: production pulls the image
platform creates a container from orders-api:abc123
runtime configuration and secrets are injected
Step 5: entrypoint starts
./bin/orders-api serve --host 0.0.0.0 --port 8080
Step 6: app validates configuration
config from lesson 012 is parsed and checked
Step 7: readiness check passes
platform sends traffic to the instance
Output:
production traffic reaches a known runtime artifact
Now compare the broken path:
CI:
tests run on host machine
host has libpq installed
Production:
image is built separately
image does not include libpq
container starts
process crashes before readiness
The missing evidence was:
Does the deployable image contain the runtime assumptions the app needs?
Testing source files on a host machine is useful. It is not the same as testing the artifact you deploy.
Check: CI tests source code on the host machine, then production builds a fresh image from a different base image. What evidence is missing?
Think first, then reveal.
Answer: Evidence that the deployable image itself works. If production runs a container image, the pipeline should test that image or the same build output that becomes the image.
Local Development Is Not the Artifact
Local development stacks are useful.
A compose.yaml or similar file might start:
orders-api
postgres
redis
fake-payment-provider
That helps developers run the system without manually installing every service.
But the local stack is not the production artifact.
The application image is one artifact. PostgreSQL, Redis, and the fake payment provider are support services. Production may use a managed database, a cloud queue, and a real payment API.
Two mistakes come from confusing these:
First, baking dependency services into the app image.
bad image:
orders-api process
PostgreSQL server
queue server
test payment fake
A web API image should usually run the web API process. External services have different lifecycles, backups, scaling needs, access controls, and failure modes.
Second, treating local convenience as production readiness.
Local development may:
- mount source code directly,
- run a debug server,
- disable TLS,
- use weak passwords,
- skip production build steps,
- include fake providers,
- restart instantly on file changes.
Those are fine when explicit. They are dangerous when they silently become the deployment artifact.
Check: Should the production orders-api image contain production database files so the app and database always move together?
Think first, then reveal.
Answer: Usually no. The app image and database state have different lifecycles. The database needs backups, migrations, storage, access control, and operational handling outside the app image.
Entrypoints, Readiness, and Shutdown
The entrypoint is the command the platform starts.
For the web API:
./bin/orders-api serve --host 0.0.0.0 --port 8080
For a worker:
./bin/orders-api worker --queue orders-production
Entrypoints matter because the deployment platform manages the process. If the entrypoint starts a child process in the background and exits, the platform may think the app stopped. If it catches errors and keeps running, the platform may think the app is healthy when startup failed. If it ignores shutdown signals, deploys may drop in-flight requests.
A good production entrypoint should:
- start the intended production process,
- fail loudly if startup cannot complete,
- write logs to stdout or stderr,
- keep the main process in the foreground,
- respond to shutdown signals,
- let configuration validation fail before readiness.
Readiness is different from liveness.
liveness:
should the platform restart this process?
readiness:
should this instance receive traffic?
For orders-api, readiness might require:
config loaded
server accepts requests
database connection is usable enough
required migration version is compatible
A bad readiness check says:
process exists -> ready
That can route traffic to an instance that started but cannot connect to the database. A readiness check that is too deep can also be harmful if it depends on a slow optional service. The right readiness check should reflect what this service must have to answer its core requests.
Artifact Review Table
When reviewing a deployment artifact, ask which boundary each item belongs to.
Item Belongs in image? Runtime boundary?
application code yes no
language runtime yes no
native library needed to run yes no
production API key no secret injection
DATABASE_URL no config injection
debug shell usually no emergency tooling elsewhere
PostgreSQL data files no database service
fake payment provider no local/test dependency
entrypoint command metadata platform starts it
readiness check app/platform contract traffic decision
This table is not a universal law. Some debugging tools may be included intentionally. Some local images may be different from production images. The important habit is to make the decision explicit.
The deployment artifact should also be traceable:
source commit: abc123
image tag: orders-api:abc123
image digest: sha256:...
build time: 2026-07-01T10:15:00Z
latest is convenient for local experiments. It is weak release evidence in production because it does not identify exactly what is running.
Incident Signals
When a containerized backend fails, ask which boundary produced the bad evidence.
Use a small incident table:
Symptom Likely boundary to inspect
process cannot find library image build
config validation fails runtime configuration
secret appears in image history artifact/secret boundary
wrong server command starts entrypoint
traffic reaches broken instance readiness
old code and new image unclear traceability
container restarts repeatedly startup, liveness, or crash loop
slow shutdown drops requests signal handling
This table keeps diagnosis concrete.
If the process cannot find libpq, inspect the image build. Did the build recipe install the native package? Did tests run against the same image?
If startup says DATABASE_URL is missing, inspect runtime configuration. The image may be fine. The environment did not inject the value the app needs.
If traffic reaches an instance that cannot talk to the database, inspect readiness. The process may be alive, but the platform trusted it too early.
If nobody knows which code is running, inspect traceability. A digest, commit SHA, build ID, and deployment event should let operators connect the running container back to source and pipeline evidence.
The useful habit is to avoid one vague sentence:
Docker is broken.
Replace it with a boundary question:
Did the image, runtime config, entrypoint, readiness check, or release identity lie?
Trade-offs and Limits
Containers buy runtime reproducibility. More of the backend's runtime assumptions travel with the artifact.
They cost build discipline. Images can become large, slow to build, full of unnecessary packages, or hard to debug if the build recipe is unclear.
The main trade-off is reproducibility versus image complexity and attack surface.
Smaller images transfer faster and usually include fewer unnecessary tools. Larger images may be easier to inspect during debugging, but they carry more packages and more things to patch.
Pinned base images make builds more repeatable, but they need planned updates. Floating tags are convenient, but they can change without source code changing.
Containers do not replace:
- database migrations,
- secret management,
- configuration validation,
- observability,
- backups,
- release checks,
- application-level tests.
They make the runtime package easier to reason about. Other boundaries still exist.
You can see the boundary is weak when:
- the image lacks a library that existed on the developer machine,
- production uses a development entrypoint,
- a secret is baked into the image,
- readiness passes before the app can serve,
- nobody can connect a running container back to a source commit.
Practice
Review this deployment artifact:
Image:
tag: orders-api:latest
includes source code, runtime, dev compiler, debug shell
has PAYMENT_API_KEY baked into image at build time
Entrypoint:
./start-dev-server
Health:
readiness returns 200 if process is running
Deployment:
production injects DATABASE_URL at runtime
A good answer should mention:
orders-api:latestis weak traceability; use an immutable tag or digest tied to a commit or build.- Baking
PAYMENT_API_KEYinto the image violates the secret/config boundary from lesson 012. - A production image should usually exclude dev-only compilers or debug tools unless there is an explicit reason.
./start-dev-serveris the wrong entrypoint for production unless it is actually the production server.- Readiness is too shallow if it only checks process existence.
- Injecting
DATABASE_URLat runtime is the right boundary, but startup validation must still check it.
Model answer:
This artifact should not be promoted as-is. The tag does not identify the exact release, the image contains a secret, the entrypoint suggests a development server, and readiness can send traffic to a container that cannot serve real requests. A safer shape uses orders-api:<commit-sha> or an immutable digest, excludes production secrets, injects configuration at runtime, starts the production command, validates config at startup, and reports readiness based on the service's real requirements.
Connections
Lesson 012 explained why configuration and secrets belong at runtime boundaries. This lesson applies that idea to images: the image should package the runtime, not production secrets or external service state.
The next lesson uses this artifact as one part of a release pipeline: build once, test the artifact, coordinate migrations, roll out, and stop when checks fail.
Resources
- [DOC] Docker: What is an image?
- Link: https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-an-image/
- Focus: Use this to understand images as packaged filesystem and metadata.
- [DOC] Docker: Multi-stage builds
- Link: https://docs.docker.com/build/building/multi-stage/
- Focus: Read for separating build-time tools from smaller runtime images.
- [DOC] Kubernetes: Liveness, Readiness, and Startup Probes
- Link: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
- Focus: Compare probes as different operational signals.
- [REFERENCE] Open Container Initiative: Image Format
- Link: https://github.com/opencontainers/image-spec/blob/main/spec.md
- Focus: Skim for the standard shape behind container image metadata and layers.
Key Takeaways
- A container image packages runtime assumptions; a running container adds runtime config, network, and platform behavior.
- Local development stacks are support environments, not automatically the production artifact.
- Secrets, database state, and provider configuration should stay outside the app image.
- Entrypoints and readiness checks decide how the platform starts, trusts, and stops a container.
- Good deployment artifacts are reproducible, identifiable, free of baked-in secrets, and validated before receiving traffic.
← Back to Backend Development Foundations