Skip to content

Cloud & Infrastructure

Docker Consulting & Engineering

Docker is a packaging format and a build tool, not a production platform. Used for what it is, it removes an entire category of problems. Mistaken for an orchestrator, it creates a new one.

Overview

Docker packages an application together with everything it needs to run, its libraries, system tools, runtime and configuration, into an image that behaves the same way on a laptop, on a continuous integration runner and on a production host. It achieves this with operating system level virtualisation rather than hardware virtualisation: containers share the host’s Linux kernel and are isolated using namespaces, which control what a process can see, and cgroups, which control what it can consume. There is no guest operating system inside a container. That single architectural fact is why containers start in milliseconds instead of tens of seconds, weigh megabytes instead of gigabytes, and let you run dozens on a machine that would struggle with a handful of virtual machines.

The win most teams feel first is the end of "works on my machine". An image built from a Dockerfile is a specific, addressable artefact: the same instructions produce the same layered filesystem, and the thing a developer tested is the thing that ships, bit for bit. Docker Compose extends that from one process to a whole environment, your application plus its database, cache and message broker, described in a single file and started with one command. For onboarding, for reproducing a bug a colleague hit, and for giving continuous integration a clean disposable environment on every run, that alone justifies the adoption. New engineers stop losing their first two days to a page of setup instructions that went stale six months ago.

It is worth being precise about what "Docker" now means, because the vocabulary has drifted. There is the image format and the runtime, both standardised under the Open Container Initiative, which is why an image built with Docker runs on containerd, on Podman, on Kubernetes and on every managed container service without translation. There is the Docker Engine and CLI, the day-to-day tool. There is BuildKit, the modern build engine that handles parallel stages, cache mounts and secret mounts. And there is Docker Desktop, a commercial product with licensing terms that apply to larger organisations, which is a procurement question rather than a technical one but catches teams by surprise often enough to be worth flagging. The image format is the durable, portable part, and it is the part your architecture should depend on.

The most important thing to be clear about is where Docker stops. It builds and runs individual containers extremely well. It does not decide which host a container should run on, restart it somewhere else when a machine dies, roll a new version out gradually while watching health checks, give it a stable network identity as replicas come and go, or balance traffic across a fleet. Those are orchestration concerns and they belong to Kubernetes, Amazon ECS, a managed container platform or, for a genuinely small system, a well-configured process supervisor on one machine. Teams who try to cover that ground with Docker alone end up with a collection of shell scripts and a cron job holding production together, which works right up until the evening it does not.

So we treat Docker as the foundation layer, and we treat it seriously in its own right, because the quality of the image determines how well everything above it behaves. A bloated image is slow to pull on every deployment and every scale-up event. An unscanned base image ships known vulnerabilities into production with your name on them. A container running as root with a writable filesystem hands an attacker far more than it needs to. A stateful workload treated as stateless loses data at the worst possible moment. None of these are exotic failure modes. They are the ordinary consequences of writing a Dockerfile the way the first tutorial suggested and never revisiting it, and they are precisely the things we fix.

Best for: Teams who want reproducible, portable builds and a hardened packaging layer that feeds cleanly into whatever runtime they use, without dragging a full virtual machine around with every deployment.

Why teams choose Docker

  • Reproducible on every machine

    A Dockerfile produces the same layered image wherever it is built, so the environment a developer tested is the one that reaches production. Environment drift, the class of bug that quietly eats afternoons and produces the phrase "it works locally", largely disappears. A new engineer gets a running stack in minutes rather than a day of following setup notes that stopped being accurate two releases ago.

  • Lighter and faster than virtual machines

    Sharing the host kernel means a container starts in a fraction of a second and costs megabytes rather than gigabytes. You pack far more workloads onto the same hardware, stand up ephemeral environments cheaply for every test run, and scale out with almost no boot latency, which is what makes autoscaling feel responsive rather than sluggish.

  • A portable artefact, not a platform commitment

    An OCI image is self-contained and runs unchanged on a laptop, in continuous integration, on any container service and on any cloud. That portability is what keeps your build pipeline decoupled from where the thing eventually runs, and what lets you start on one host and adopt an orchestrator later without redoing the packaging.

  • A smaller, more defensible attack surface

    Done properly, containerisation reduces exposure rather than adding to it. A multi-stage build ships the compiled artefact and nothing else: no compilers, no package manager, no build caches, no source. Add a non-root user, a read-only root filesystem and dropped capabilities, and the thing running in production contains almost nothing an attacker can use.

  • Builds that finish in seconds

    Layer caching, when the Dockerfile is ordered for it, means a code change rebuilds one layer rather than the whole image. With BuildKit cache mounts for dependency downloads and a sensible .dockerignore, the difference between a considered build and a careless one is often a ten-second pipeline against a five-minute one, on every commit, for every engineer.

Why businesses choose Docker

  • It is the de facto standard for building container images, and the format is an open standard, so the tooling, registries, cloud integrations and hiring pool all assume it. You are never on an island.
  • It decouples your build from your runtime. You can start on a single host and adopt Kubernetes, ECS or a managed platform years later without rebuilding your packaging layer, which is unusually good insurance for the effort involved.
  • The gap between an average Dockerfile and a good one is large and entirely addressable: smaller images, faster builds, fewer vulnerabilities and a container that cannot write to its own filesystem. It is one of the highest-return pieces of engineering hygiene available.
  • It makes local development match production closely enough that "we could not reproduce it" stops being an acceptable answer to a bug report.

What we build with Docker

The capabilities this technology is genuinely strong at, and what we most often build with it.

  • Dockerfiles and layer caching

    An image is built from an ordered set of instructions, each producing a cached filesystem layer. The order matters enormously: put the steps that rarely change first, so dependency installation is cached and a code change rebuilds only the final layers. Copy your lockfile and install dependencies before copying the source, not after. Combine related commands so an artefact removed in a later instruction does not linger invisibly in an earlier layer. We write Dockerfiles that build fast and remain legible six months later, which are usually the same Dockerfiles.

  • Multi-stage builds

    A build stage compiles or bundles the application with the full toolchain, and a final stage copies only the resulting artefact into a minimal runtime image. This is how you ship forty megabytes instead of eight hundred: no compilers, no package manager, no build cache, no source code in the thing that runs in production. It shrinks pull times, cold-start times and the attack surface simultaneously, and it is the single highest-value change we make to most existing Dockerfiles.

  • Base image choice and pinning

    The base image is the largest security and size decision in the file, and it is usually made carelessly. Alpine is tiny but uses musl, which occasionally breaks native extensions in ways that are painful to debug. Slim variants of Debian-based images are a sensible default for most applications. Distroless images contain a runtime and nothing else, no shell, no package manager, which is excellent for production and mildly inconvenient when you need to inspect a running container. We pin to digests rather than mutable tags so a base image cannot change under you between two builds of the same commit.

  • BuildKit, cache mounts and build secrets

    BuildKit is the modern build engine and it changes what is possible. Stages that do not depend on each other build in parallel, cache mounts let a package manager keep its download cache between builds without baking it into a layer, and secret mounts make a credential available during a build without it ever appearing in the image history. That last point matters: a secret passed as a build argument is recoverable from the image forever, and BuildKit is how you avoid it properly.

  • Docker Compose for local environments

    One YAML file describes the whole stack, application, PostgreSQL, Redis, whatever else, with networks, volumes, health checks and environment wired together. New engineers run one command and get a working system. A colleague reproduces your bug by checking out your branch. Integration tests run against real dependencies rather than mocks that drift from reality. Compose is for development and continuous integration; we are equally clear that it is not a production orchestration story, and we do not pretend otherwise.

  • Registries, tagging and distribution

    Images are pushed to and pulled from a registry, and how they are tagged determines whether a deployment is reproducible. We treat immutable digests as the deployment reference and use tags as human-readable labels, because a mutable "latest" in production means you cannot say with certainty what is running. Private registries with proper credentials, retention policies so old images do not accumulate indefinitely, and image signing where the supply chain warrants it.

  • Volumes, state and backup

    Containers are ephemeral and their writable layer disappears when they do. Anything that must survive, database files, uploads, generated content, lives in a named volume or a bind mount backed by real storage, with a backup routine that somebody has actually tested by restoring from it. We are deliberate here because the failure mode is not downtime, it is data loss, and it is discovered late.

  • Image hardening and vulnerability scanning

    A container should run as a non-root user, with a read-only root filesystem, dropped Linux capabilities and no more mounted into it than the job requires. Scanning runs in the pipeline so a known critical vulnerability fails the build rather than reaching production, and images are rebuilt on a cadence because a clean image ages. Where the supply chain matters we generate a software bill of materials so you can answer "are we affected" in minutes rather than in a frantic afternoon.

  • Health checks, signals and graceful shutdown

    The details that decide whether an orchestrator can do its job. A container needs a real health check rather than a process that is technically alive, it needs to receive and act on the termination signal rather than being killed after a timeout, and it needs to avoid a shell wrapper that swallows signals so the application never hears them. Getting this wrong produces dropped requests on every deployment, and it is invisible until somebody looks for it.

Use cases

  • Consistent local development environments

    A Compose stack replaces a page of setup instructions with one command. Every engineer runs the same database version, the same cache, the same service topology and the same configuration shape, so a new starter is productive on their first day rather than debugging their own machine. The secondary benefit is larger: when a bug is reported, anyone on the team can reproduce the exact environment it appeared in.

  • Build and test pipelines

    Containers give continuous integration a clean, disposable environment for every run, so no state bleeds between builds and nobody loses an afternoon to a runner that had a different toolchain version. More importantly, the image tested in the pipeline is the exact artefact promoted onwards, so the tests verify the real thing rather than an approximation of it built by different means.

  • Packaging services for an orchestrator

    Services are built as images and handed to Kubernetes, ECS or a managed platform, which schedules, scales and heals them. Docker is the packaging step and the orchestrator is the runtime. Getting the image right, small, correctly configured, honest about its health, responsive to signals, is what makes the orchestration layer behave predictably, and most orchestration pain traces back to an image that was never designed for it.

  • Stabilising a fragile legacy application

    Wrapping an older application in a container pins its runtime, its dependency versions and its configuration in place, turning something that only runs on one carefully maintained machine into something reproducible and movable. It is often the pragmatic first move in a modernisation programme, because it makes the system safe to touch before anybody attempts to change it, and it removes the "nobody dares reinstall that server" problem immediately.

  • Isolating awkward tooling and one-off jobs

    Data processing scripts, report generation, machine learning inference, anything with a heavy or conflicting set of dependencies. Putting it in a container means it runs identically on a laptop, on a schedule and in a pipeline, without contaminating a host with a specific Python or Node version that something else needs to be different.

  • Cutting an over-inflated image estate down to size

    A common and satisfying engagement. An organisation with images approaching a gigabyte, slow deployments, long cold starts and a vulnerability report nobody wants to read. Multi-stage builds, a minimal pinned base, a real .dockerignore, layer reordering and scanning in the pipeline typically transform all four of those at once, and the change is measurable rather than a matter of taste.

When Docker is the right choice

  • Right when you want genuine parity between development, continuous integration and production. Every engineer, every build runner and every server executes an identical image, environment drift stops being a category of bug, and onboarding collapses to a single command.
  • Right when you are packaging services for any modern cloud platform. Kubernetes, ECS, Fargate, Cloud Run, Azure Container Apps and every managed container service take an OCI image as the unit of deployment, so containerising is the entry ticket rather than an optional refinement.
  • Right when you need lightweight, fast-starting isolation for many small services on shared hardware, where full virtual machines would waste memory on duplicate operating systems and waste minutes on boot time.
  • Right when you need to pin a fragile application’s runtime in place. Wrapping an older system in a container freezes its dependency versions and configuration, which is often the pragmatic first step of a modernisation programme: stabilise it, make it movable, then refactor.
  • Wrong when you need a strong security boundary between untrusted tenants or untrusted code. Containers share the host kernel, so a kernel escape crosses the boundary. The honest answer there is a virtual machine, or a sandboxed runtime such as gVisor or Firecracker, and we will say so rather than sell you a fence that does not reach the ground.
  • Wrong, or at least premature, when you run a single application on a single server with no scaling story and a team of two. A plain system service or a managed platform may well be simpler to operate than a container toolchain, a registry and a build pipeline that you now have to maintain alongside the product.
  • Wrong as a way of avoiding a decision about state. Containerising a database because everything else is containerised is not an architecture, and it is the source of a disproportionate share of the data-loss stories in this field.

Docker: pros and cons

Strengths

  • It ends environment-specific bugs. The built image is identical across development, continuous integration and production, so problems stop hiding in the gaps between them.
  • Extremely lightweight and fast to start compared with virtual machines, which improves both hardware density and the speed of the development loop.
  • Compose makes multi-service local environments trivial to stand up, tear down and version alongside the code, which is the single most effective onboarding tool most teams have available.
  • The image format is an open standard under the OCI, so images feed directly into Kubernetes, ECS, Cloud Run, Podman and every serious platform without rework or vendor negotiation.
  • The ecosystem is mature and thoroughly documented: base images for every runtime, scanners, registries, signing tooling and first-class support in every continuous integration system.
  • The learning curve to productive is genuinely short. A Dockerfile and a Compose file give a team reproducible environments in an afternoon, which is rare for infrastructure tooling.

Trade-offs

  • Docker is not orchestration, and confusing the two is the most expensive mistake in this area. It does not schedule across hosts, self-heal after a node failure, perform a gradual rollout with automatic rollback, or provide service discovery and load balancing. If you need those, you need an orchestrator underneath, and Docker is the layer that builds what it runs.
  • Image bloat is the default outcome, not the exception. A naive Dockerfile built on a full operating system base with the toolchain left in the final image routinely produces something close to a gigabyte, when the same application shipped from a multi-stage build on a minimal base might be forty megabytes. You pay for that difference on every pull, every deployment, every scale-up and every cold start, and it carries hundreds of packages you never use but must now patch.
  • Stateful workloads need genuine care and are easy to get subtly wrong. Container filesystems are ephemeral by design, so anything that must survive belongs in a named volume backed by real storage, with a backup strategy that has been tested by restoring from it. The most common Docker disaster is not dramatic: it is a database container treated as disposable, and the loss discovered later.
  • Shared-kernel isolation is weaker than a virtual machine. A kernel vulnerability exploited from inside a container can reach the host, which makes containers a poor boundary between mutually untrusted workloads regardless of how carefully the rest is configured.
  • Security scanning is a discipline you have to impose, not a default you inherit. Every base image carries the vulnerabilities of everything inside it, public images change under you if you pin only to a mutable tag, and an image that was clean when it was built is not clean six months later. Without scanning in the pipeline and a rebuild cadence, you are shipping known vulnerabilities with a straight face.
  • On macOS and Windows, Docker runs inside a lightweight virtual machine, so bind-mount filesystem performance can be genuinely poor for workloads that touch many files. It is workable with named volumes and cache strategies, but it surprises teams who assumed the experience matches Linux.
  • The toolchain is one more thing to own. Registries, credentials, build caching, base image updates and, for larger organisations, Docker Desktop licensing all become somebody’s responsibility, and a small team should count that cost honestly before adopting.

What a container actually is

Docker builds on Linux kernel primitives that predate it. Namespaces isolate what a process can see: its own process tree, network stack, mount table, hostname and user mappings. Cgroups limit what it can consume: CPU shares, memory ceilings, block I/O. A container is simply one or more processes running under those constraints on the host kernel. There is no guest operating system, no hypervisor and no virtual hardware, which is exactly why it starts instantly and costs almost nothing to run, and equally why its isolation boundary is weaker than a virtual machine’s.

Images are immutable and layered. Each instruction in a Dockerfile produces a read-only layer, and at runtime a thin writable layer is added on top through a copy-on-write filesystem. Layers are content-addressed and shared, so ten services built on the same base store that base once and pull it once. This is also why a file deleted in a later instruction is still present in the image: the earlier layer still contains it, and anything sensitive written there is recoverable by anyone who can pull the image. Understanding layering is most of what separates a small, fast, honest image from a large one.

The runtime stack has become properly modular, which matters for your architecture even if you never think about it. Docker delegates to containerd, which delegates to runc, and all of it speaks the Open Container Initiative image and runtime specifications. That is why an image built with Docker runs on Kubernetes, which has not used Docker as its runtime for some years, and on Podman, and on every managed container service. Your dependency is on an open format rather than on one vendor’s tool, which is a genuinely strong position to be in.

So we design the image rather than merely writing a Dockerfile. That means an explicit boundary between build time and run time through multi-stage builds, a deliberately chosen and digest-pinned base, a non-root user, a read-only root filesystem where the workload permits, configuration injected through environment and mounted secrets rather than baked into a layer, a real health check, and correct signal handling so shutdown is graceful. The output is an artefact you can reason about: small, reproducible, and honest about what is inside it.

Runtime overhead, build speed and the macOS caveat

At runtime on a Linux host, the container abstraction is close to free. There is no operating system to boot, so start-up is the process start-up. CPU and memory overhead relative to running the same process directly on the host is negligible, because the process genuinely is running directly on the host, just under kernel constraints. This is what makes containers viable both for high-density hosting and for ephemeral test environments that are created and destroyed thousands of times a day.

The honest exceptions are storage and networking. Copy-on-write storage drivers add measurable overhead for write-heavy workloads, which is one of several reasons heavy database I/O belongs on a properly configured volume or, more often, on a managed database outside containers entirely. The default bridge network adds a layer of network address translation; host networking removes it where the trade-off in isolation is acceptable. And on macOS and Windows, Docker runs inside a lightweight virtual machine, so bind mounts cross a filesystem boundary and can be slow enough to change how a development loop feels. We work around that with named volumes, selective sync and cache strategies where it matters, but it is worth knowing rather than discovering.

Most real performance work with Docker is on the build rather than the runtime, because that is where engineers spend their attention every day. Ordering instructions so the cache actually hits, using BuildKit cache mounts so package managers keep their downloads between builds, a strict .dockerignore so a large local directory is not sent to the build context on every run, and parallel stages where they are independent. The difference between a well-structured build and a careless one is routinely minutes per commit, multiplied by every engineer and every pipeline run, which adds up to a substantial amount of collective waiting.

Image size is a performance property too, and an underrated one. Every megabyte is pulled on every deployment to every new host, on every autoscaling event, and on every cold start where the platform has not cached the layers. A forty megabyte image and an eight hundred megabyte image behave very differently under a traffic spike, and the gap is usually pure waste rather than functionality.

Supply chain, least privilege and the boundary that is not there

The first thing to be blunt about is the isolation boundary, because the industry is casual about it. Containers share the host kernel. A kernel vulnerability exploited from inside a container can reach the host, and from there other containers. For running untrusted code, or for separating tenants who should not be able to reach each other under any circumstances, that is not a strong enough fence. The honest answers are a virtual machine per tenant, or a sandboxed runtime such as gVisor or Firecracker that puts a real boundary back in place. We will tell you this rather than sell you container isolation as something it is not.

Within that model, the discipline is supply chain. Every base image carries the vulnerabilities of everything inside it, and a full operating system base carries hundreds of packages your application never invokes but which are nonetheless present, exploitable and yours to patch. So we build on minimal or distroless bases, pin to digests rather than mutable tags so a build is reproducible and cannot shift under you, run vulnerability scanning in the pipeline so a known critical issue fails the build rather than reaching production, and set a rebuild cadence, because an image that was clean when it was built is not clean three months later. Where the requirement warrants it, we generate a software bill of materials, so that when the next widely publicised vulnerability lands you can answer "are we affected" from a query rather than from an afternoon of archaeology.

Then least privilege inside the container. Run as a non-root user, because root inside a container is uncomfortably close to root outside one under some configurations, and there is rarely a reason for it. Mount the root filesystem read-only and give the process a writable volume only where it genuinely needs to write. Drop Linux capabilities the workload does not use. Do not mount the Docker socket into a container unless you fully understand that you have just granted it control of the host, which is the most commonly underestimated misconfiguration in this area. Set resource limits so one container cannot starve its neighbours.

Secrets deserve their own paragraph because the failure is permanent. A credential passed as a build argument or copied into a layer is in the image history forever, retrievable by anyone who can pull it, and rebuilding without it does not remove it from images already distributed. Secrets belong injected at runtime from a secrets manager, or mounted at build time through BuildKit’s secret mounts, which never persist to a layer. Alongside that, registries are part of your perimeter: private where appropriate, credentials scoped, images signed where the supply chain justifies it, and deployment referencing immutable digests so nobody can quietly change what "latest" means.

None of this is exotic and none of it is on by default. It is a set of habits enforced in the pipeline, so that security is applied on every build rather than remembered when somebody has time.

Where Docker stops scaling, and what takes over

Docker scales a single host perfectly well. You run more containers until CPU or memory runs out, with resource limits keeping them from starving each other. For a lot of systems that is genuinely sufficient for longer than people expect, and a single well-provisioned machine running a handful of containers is a legitimate production architecture rather than an embarrassment.

What Docker does not do is scale across hosts. There is no built-in scheduler to decide where a container should run, nothing to restart it elsewhere when a machine dies, no stable networking identity as replicas come and go, no gradual rollout that watches health checks and rolls back, and no load balancing across instances. This is the precise line where orchestration begins. Pretending Docker covers it is how teams end up with a set of deployment scripts, a cron job and an undocumented convention about which server runs what, and that arrangement fails at the worst possible time because nobody has ever tested what happens when a host disappears.

For multi-host scale we hand images to whatever runtime fits the situation, and the choice is a real one rather than a foregone conclusion. A managed container service such as ECS with Fargate, Cloud Run or Azure Container Apps handles scheduling, health, scaling and rollouts without you operating a cluster, and covers the requirements of a great many teams. Kubernetes is the answer when the scale, the number of independently deploying teams, or the need for a consistent platform across environments genuinely justifies its operational weight. In every case the image is the contract between our build and their runtime: if it is small, correctly configured, stateless where it should be, honest in its health check and well behaved on shutdown, the orchestrator’s job is easy.

Stateful services scale differently and deserve caution rather than enthusiasm. Databases, queues and anything holding data do not become scalable by adding containers. They need a storage strategy, replication, backups that have been restored in a rehearsal, and usually a managed service rather than a container at all. We keep state deliberately at the edges of the containerised layer and design the middle to be as stateless as the domain honestly allows, which is what makes horizontal scaling a configuration change rather than a project.

Docker integrations & ecosystem

The technologies we most often pair with it. Each links to how we work with it.

How we approach containerisation

We start from how you build and ship today, because a good Dockerfile is shaped by where the image is going. What languages and build systems, what the application assumes about its filesystem and its configuration, where state lives, what the deployment target is or will be, and how long the current build takes. That last question is more informative than it sounds: a slow build is usually a symptom of layer ordering and context size, and fixing it is a quick, visible win that buys credibility for the rest of the work.

Then we build the packaging layer deliberately. Multi-stage builds with a pinned minimal base, a strict .dockerignore, non-root users and read-only filesystems where the workload permits, real health checks and correct signal handling, a Compose setup that makes local development a single command, and scanning wired into the pipeline from the outset rather than added after an audit asks for it. We look hard at state early, because deciding where data lives is an architectural decision that gets more expensive to revisit with every month it is deferred.

We are explicit about the boundary between Docker and orchestration, in both directions. If you need multi-host scale, we design the hand-off at the same time, so the images we build are the images that run and there is no second containerisation exercise later. If you do not need multi-host scale, we will say that too, and leave you with a simple deployment you can actually operate rather than a platform you will resent.

Because we operate what we build, we optimise for the things you feel in production rather than the things that look tidy in a demo: images that pull quickly, builds that finish before attention wanders, containers that shut down without dropping requests, and a vulnerability report that is short enough to read.

The service behind it

Delivered throughCloud Engineering

What we build with Docker

The disciplines this technology most often shows up in, from a first build to taking over and stabilising an existing one.

How we deliver

  1. 01

    Discover

    We map the system, the constraints and the business it serves, including the parts nobody documented.

    Architecture brief

  2. 02

    Architect

    Decisions get made, written down and defended before a line of production code exists.

    Decision records

  3. 03

    Build

    Short cycles against working software. You see progress in the product, not in a status deck.

    Shipping increments

  4. 04

    Operate

    Monitoring, incident response and iteration. The system is alive, so the engagement is too.

    Runbooks & SLOs

Weighing up Docker?

A short call with engineers who build in it and operate the result. If Docker is the wrong tool for what you are doing, we would rather tell you now than bill you later.

Industries we use Docker in

Domain knowledge changes what gets built. A few of the sectors we know before the first meeting.

Also in Cloud & Infrastructure

Related terms

Docker compared

Why teams choose us for Docker

  • We operate what we build

    Our Dockerfiles are shaped by having been paged rather than by tutorials. That is why we care about pull times, graceful shutdown and honest health checks: they are the things that hurt in production, and they are cheap to get right at the start and awkward to retrofit after an incident has made the case for them.

  • Senior engineers only

    You work directly with people who have containerised real systems, handed them to real orchestrators and dealt with the consequences. No juniors learning on your infrastructure, and no account manager standing between you and the person actually writing the build.

  • Measurable rather than aesthetic

    Containerisation work should produce numbers: image size before and after, build time before and after, critical vulnerabilities before and after. We measure at the start so the improvement is demonstrable, because "cleaner" is not a deliverable and should not be sold as one.

  • Honest about the boundary

    We will tell you plainly when Docker is not the answer: when a workload needs a virtual machine for real isolation, when your data belongs in a managed database rather than a container, when a managed platform beats anything bespoke we could build, and when your two-server system does not need any of this yet.

Typical timeline

  1. 01

    Assess

    We map your services, their build systems, their dependencies, where state lives and what the deployment target is, and identify where containerisation genuinely helps against where it would add toil. This is also where we measure current image sizes, build times and vulnerability counts, so the improvement is a number rather than an impression.

  2. 02

    Build

    Multi-stage Dockerfiles with pinned minimal bases, a strict build context, layer ordering that makes the cache work, and a Compose stack that gives every engineer the whole system with one command. Builds are wired into your pipeline so the image tested is the image promoted.

  3. 03

    Harden

    Non-root users, read-only root filesystems, dropped capabilities, secrets injected at runtime rather than baked in, vulnerability scanning failing the build on known critical issues, a rebuild cadence agreed, and volumes plus tested backups designed for anything stateful.

  4. 04

    Hand off

    Integration with your runtime, whether that is Kubernetes, a managed container service or a single host, documentation of the build and run model written for somebody who did not build it, and a walkthrough so your team can maintain the Dockerfiles rather than treating them as untouchable.

How pricing works

  • A paid containerisation assessment first, as a fixed-scope piece of work: a review of your current build and deployment, image size and vulnerability analysis, a look at where state lives and what it would take to containerise safely, and a written recommendation including whether the deployment target should be a managed container service, an orchestrator or a single well-run host. It stands alone as a deliverable and is the cheapest way to find out what the rest of the work involves.
  • Fixed-scope engagements for a defined outcome: containerising a set of services, rebuilding an existing image estate to multi-stage hardened builds, setting up a Compose-based development environment, or wiring scanning, signing and a private registry into an existing pipeline.
  • A monthly senior engagement for teams who want ongoing platform engineering capacity, covering build pipelines, base image maintenance and rebuild cadence, the orchestration hand-off and production hardening, without taking on a permanent hire.
  • Your infrastructure bill stays yours and is unmarked-up. Registries, build minutes, container hosting and any commercial tooling or licences sit on your own accounts, invoiced to you directly by the provider at their prices, with no reseller margin and no commission to us anywhere in the arrangement. That is worth stating plainly because it means we have no incentive to recommend a heavier platform than you need, and it is why we will happily tell you when a managed container service costs less than the bespoke tooling we could build you.

Hire Docker engineers

Need Docker capacity on your own team? We embed named senior engineers into your existing team (reporting to your leads, working in your rituals), so you add capacity without a hiring cycle.

Hire Docker engineers

Common questions

What is the real difference between a container and a virtual machine?

A virtual machine includes a full guest operating system and is isolated at the hardware level by a hypervisor, so it boots in tens of seconds, costs gigabytes and provides a strong boundary. A container shares the host kernel and is isolated using kernel features, namespaces and cgroups, so it starts in milliseconds, costs megabytes and provides a weaker boundary. Neither is better in general. Use containers for density, speed and packaging. Use virtual machines when you need strong isolation between workloads that do not trust each other, or when you need a different kernel entirely.

Is Docker enough to run our application in production?

For a single service on a single host with a modest availability requirement, often yes, and we would rather say that than talk you into a platform. What Docker does not do is place containers across a fleet, restart them when a machine fails, roll out a new version gradually with automatic rollback, provide stable service discovery, or load balance across replicas. The moment you need any of those, you need an orchestrator, and the honest first step is usually a managed container service rather than Kubernetes. Docker remains the layer that builds the images they run.

Can we run our database in a container?

In development, absolutely, and it is one of the best things about Compose. In production it is a judgement call that usually goes the other way. Running a database in a container means taking full responsibility for volume management, storage performance, backup and restore, failover and version upgrades, and the failure mode is data loss rather than downtime. For most teams a managed database is more reliable and considerably less work, and it lets the containerised layer stay genuinely stateless. Where an in-container database is genuinely the right answer, it needs deliberate storage design and a restore procedure that has been rehearsed, not assumed.

Why is our image so large, and does it matter?

It is almost always because the final image is built on a full operating system base and still contains the build toolchain, package manager caches and the source code, none of which are needed to run anything. It matters more than people expect. Every megabyte is pulled on every deployment to every host, on every autoscaling event and on every cold start, so image size shows up as deployment latency and as slower recovery under load. It is also a security surface: hundreds of packages you never invoke are still yours to patch and still appear in every vulnerability report. Multi-stage builds and a minimal pinned base routinely reduce an image by an order of magnitude without changing the application at all.

How do you handle secrets in Docker?

Never as build arguments and never copied into a layer, because both persist in the image history permanently and are retrievable by anyone who can pull it. Rebuilding without the secret does not remove it from images already distributed, so the only real remedy is rotating the credential. At runtime, secrets are injected from a secrets manager or the platform’s own mechanism. At build time, if a credential is genuinely needed to fetch a private dependency, BuildKit secret mounts make it available during the build without writing it to any layer. We also scan for accidentally committed credentials, because this is a mistake that is made once and then lives forever.

Does using Docker lock us into a cloud or a vendor?

No, and that is one of its genuine strengths. The image format is an open standard under the Open Container Initiative, so an image runs unchanged on any container platform, on any cloud, on your laptop and on hardware in your own building. Kubernetes has not used Docker as its runtime for years and still runs Docker-built images perfectly. We keep the build decoupled from the runtime so you can start on one host and move to a managed service or an orchestrator later without redoing the packaging. The only commercial consideration worth knowing about is Docker Desktop licensing for larger organisations, which is a procurement matter rather than an architectural one and has straightforward alternatives.

Should we use Compose in production?

Generally not, and we will not pretend otherwise to keep a setup simple. Compose is excellent for local development and for continuous integration, where its job is to bring a whole stack up quickly and reproducibly on one machine. It does not schedule across hosts, does not self-heal in any meaningful sense, and gives you no rollout strategy. For a genuinely small internal system on a single server it can be adequate, and we have seen it used that way sensibly with a process supervisor and good backups. For anything customer-facing with an availability expectation, a managed container service is a small step up in cost and a large step up in reliability.

How do we keep images up to date once they are built?

With a rebuild cadence, because an image is a point-in-time snapshot and its vulnerability profile degrades from the moment it is built. Pinning to digests makes builds reproducible, which is what you want, but it also means nothing updates until you decide to update it. So we set a regular automated rebuild that picks up patched base images, run scanning on every build so a newly disclosed critical vulnerability surfaces quickly, and make the rebuild path low friction enough that patching is routine rather than an event. A software bill of materials makes the next widely publicised vulnerability a query rather than an investigation.

Our containers drop requests during deployment. Why?

Almost always signal handling or health checks, and it is one of the most common problems we find. If the application is started through a shell wrapper, the shell receives the termination signal and the application never does, so it is killed abruptly after the grace period with requests in flight. If the health check reports success before the application is genuinely ready, traffic arrives too early. If there is no distinction between readiness and liveness, a slow start looks like a failure. The fixes are unglamorous: run the process directly or use a proper init, handle the termination signal and drain connections before exiting, and write health checks that reflect reality rather than the fact that a process exists.

Building on Docker?

Tell us what you are building and where it is stuck. A senior engineer reads it and gives you an honest read on whether Docker is the right fit for the problem, or what we would reach for instead.

  1. 01A senior engineer reads it. Not a form queue, and not an account manager.
  2. 02We reply either with questions or with a straight answer that we are not the right fit.
  3. 03If it looks like a fit, a technical call with the person who would actually run the delivery.
  4. 04Then scope, effort and risk in writing, before anyone signs anything.

Two fields required. We reply to real enquiries. No list, no sequence.