I'm setting up a fairly complex application environment and need two separate container images that share the same core setup. One image should add and run the application server, while the other should contain only utility tasks such as cron jobs. The application server will run as a scalable Kubernetes workload, while there will be a single utilities container.
I'd like to avoid putting the application server and cron tooling into the same image. Ideally, I would define the shared dependencies and configuration once—perhaps in a base Dockerfile or build stage—and then derive both images from it. I'll also eventually need development, staging, and production variants, which may use different databases, filesystems, or optional debugging tools.
What is the current recommended Docker approach for sharing the common setup while producing these separate images? Is a multi-stage Dockerfile appropriate for this, or should I maintain and build a separate base image?
3 Answers
Another option is to build and tag a standalone base image, then use it in separate Dockerfiles with `FROM your-base-image:tag`. That can be useful if the shared foundation is reused across multiple projects or needs an independent release cycle. For a single project, named stages in one Dockerfile are usually simpler and keep the shared setup in one place.
A multi-stage Dockerfile works well for this, even when you aren’t using it to remove build artifacts. Define the shared setup in a named stage, then create one stage for each final image:
`FROM ubuntu AS base`
`# shared setup`
`FROM base AS appserver`
`# install the application server`
`FROM base AS utilities`
`# install cron and utility tasks`
Build the desired final image with commands such as `docker build --target appserver -t myapp-server .` and `docker build --target utilities -t myapp-utilities .`. Each target contains the common layers but has its own role-specific software and startup command.
For development, staging, and production, you can add further build targets or use carefully scoped build arguments for genuinely build-time differences. Runtime settings—such as database endpoints, credentials, and filesystem locations—are generally better supplied by Kubernetes configuration and secrets rather than baked into the image. Optional debugging tools can be added through a development-specific target.
That makes sense. The environments mainly differ in their external services and in a few development-only tools, so I’ll keep those concerns separate from the shared image structure.

This also fits Kubernetes nicely: keep the web-serving workload and the scheduled or utility workload as separate deployments or jobs, so each can scale and restart independently.