Some time ago I've written about my experiences using it in production https://nickjanetakis.com/blog/why-i-like-using-docker-compo.... Not just for my own projects but for $500 million dollar companies and more.
We have been using it for more than five years now. Before that, we had a legacy deployment model, and I do not remember a single major issue related to Docker Compose.
We use it for both staging and production environments. The same Docker image validated in staging is deployed to production. Never fails!
This is why nobody uses it. Cloud stuff has to be as baroque as possible.
There is a theory which states that if ever anyone discovers exactly what Kubernetes is for and how it works, it will instantly disappear and be replaced by something even more bizarre and inexplicable.
What happens if it errored on deployment or after that? you wanna write custom (bash? :D) hooks for that? What about upgrading your 'very vertically scalable' box? What if it doesn't come up after the upgrade? your downtime is suddenly hours, oops.
The k8s denial is strong and now rivals frontend frameworks denial. Never fails to amuse.
https://github.com/daitangio/misterio
It works very well!
https://www.macchaffee.com/blog/2024/you-have-built-a-kubern...
But docker compose can actually be very sufficient for what many projects actually need.
Granted I am a guy pushing for compose based localdevs and such but going further you often just cannot beat the simplicity of doing update QA or other CI/CD workloads in compose based projects. I have had dozens of projects where we replaced flaky slow and maintenance heavy pipelines with just docker compose up --build --wait in the past years. How come you say health checks are still broken?
Some of my concerns with compose aren’t purely technical. It makes it easier to lean on local state like volumes, bind mounts, and large .env files. Similar mechanisms exist in kubernetes, but the additional setup tends to force a bit more thought about whether they’re actually needed or just a shortcut.
On the health check side, they exist, but compose doesn’t fully act on them, that's the part that is missing. There’s no built in remediation or orchestration behavior tied to health status, which is why things like https://github.com/willfarrell/docker-autoheal exist. It’s something that was never fully carried through in Docker itself.
Agree.
Plus there's the monitoring of the host that is always overlooked in articles. I've ended up chucking Monit on there to monitor disk usage et al, and also used it for monitoring compose too and restarting containers.
And then there's Healthchecks.io, and external uptime monitoring... the list goes on. Properly monitoring systems, even single server systems, is not simple.
While not built in k8s has at least velero and kasten. However they are only possible because of snapshots https://kubernetes.io/docs/concepts/storage/volume-snapshots... and kasten has a plugin like architecture (because of k8s ) that supports application specific backups. However I never found something like that for compose. And that is troublesome in bigger projects like sentry
Docker volumes (and bind mounts) however have the minor problem of being hard to get a consistent copy to without stopping the service. You can work around this by, e. G., having ZFS or btrfs as the underlying FS and making a snapshot there. Otherwise, your software (like PostgreSQL) might also have other online backup tooling.
It's because I like keeping my servers stateless when possible. It makes it easier to upgrade them in a zero downtime way later.
If your web server has your DB too, then you can't do zero downtime system upgrades. For example I would never upgrade Debian 12 to 13 on a live server. Instead, I'd make a new server with 13, get it all ready to go and tested and then when I'm ready flip over DNS or a floating IP address to the new server. This pattern works because both the old and new server can be writing to a database on a different server.
With all that said, if you were ok with 1 server, then yeah I'd for sure run it in Docker Compose.
How would you upgrade the server running the database?
For most DBs it's one or two paths in the container, and virtually all DBs vendors have a reference Docker Compose example somewhere showing volume config. I can't remember the last time I ever "natively" installed a DB personally!
It really comes down to YMMV... Sometimes for a singular app surface, it's easier to just use a compose file that includes the database. mailu/mailcow is a good example... you don't necessarily want to comingle email on the same server as other services.
That said, if you need to share a single DB or set of DBs across an applicaiton with several instances/deployments, then it makes much more sense for a central deployment. I almost never do my own host level install, instead relying on cloud hosting and mgt. The only real exception is MS-SQL on internal servers... MS-SQL in Docker is barely acceptable for dev, and missing a few key features you may actually want/need.
What if you can't by yourself objectively evaluate if turkey sandwich sounds good?
It's not a matter of giving a universal answer to whether docker compose in production is fine, but how to evaluate it. Which features or safeguards necessary for a healthy production environment you forfeit when choosing plain docker compose? What's the tradeoff?
Comments like this are apathetic and reduce the challenges of good software engineering to hopes and random chance.
*shudder*
Your entire original comment looks like just an opportunity to be snarky. It's a longer version of "whatever", which you can literally throw around as an answer to anything.
In case you were curious, the subheading of the article already answers the question posed by the title:
> Yes, plain Docker Compose can still run production workloads in 2026—if you close the operational gaps it leaves: cleanup, healing, image pinning, socket security, and updates.
journald will help with logs, and the pull policy[1] helps with mutable tags. What help do you need with "orphan containers"?
[0]: https://docs.podman.io/en/latest/markdown/podman-quadlet.1.h...
[1]: https://docs.podman.io/en/latest/markdown/podman-image.unit....
You shouldn't be using podman compose. It's flimsy and doesn't work very well (at least it was last time I used it prior to Podman v3), and I'm pretty sure it doesn't have Red Hat's direct support.
Instead, activate Podman's Docker API compatibility socket, and simply set your `DOCKER_HOST` env var to that socket, and from there you can use your general docker client commands such as `docker`, `docker compose` and anything else that uses the Docker API. There are very few things that don't work with this, and the few things that don't are advanced setups.
For what it's worth, podman has also a thin wrapper (podman compose) which executes `docker-compose` or the old `podman-compose`. The docs should explain which it picks.
Note:
- `podman-compose` is an early attempt at remaking `docker-compose` v1 but for Podman. This used parsed the compose config and converts them to podman commands, and executes it.
- Later Podman wrote a Docker compatible socket instead, which can work with most docker clis that accept a `DOCKER_HOST` argument, including `docker` and `docker-compose` (both v1 and v2)
- `podman compose` is a thin wrapper that automatically selects `docker-compose` or `podman-compose` depending on which is installed.
Generally all you need is podman, docker-compose (the v2 binary), and that's it. From there you can use `podman` and/or `podman compose`.
Im no fan of docker and podman by itself is a step up but orchestration headaches are enough to ruin that.
You don’t need to live at the edge of new features. Do you upgrade your fridge and your oven every two months? It’s nice when you can have something running and not worry that the next update will break your software and/or your workflow.
https://docs.podman.io/en/latest/markdown/podman-systemd.uni...
K8s as small time is overkill for sure but make sure you don't fall into this trap. https://www.macchaffee.com/blog/2024/you-have-built-a-kubern...
What it gets you is a more powerful Docker Compose running on server that you can interact with via kubectl. No SSH, no custom scripts etc. Just kubectl and YAML.
To me, if there's generally fewer than 10 actual active users at any given time and/or you can easily tolerate 30-60m of down time now and then... I'd lean into the simpler option of docker-compose. While I generally think of compose as a dev tool first, it's definitely useful sometimes.
Probably needs a real generalist though which maybe doesn't include your average dev.
> docker compose pull && docker compose up -d is a fine command if you are SSH’d into the host. At customer scale—dozens of self-managed environments behind firewalls, each with its own change-control process—that manual process doesn’t scale.
No idea what this 'customer scale' operation is, but it seems like a pretty clear cut candidate for not using docker compose. I also don't think watchtower should be listed there, it's been archived and was never recommended for production usage anyways.
Isn't that a Docker thing rather than Docker Compose though? There is a ton more caveats to add if we don't already assume the reader is familiar with the hard edges of Docker, seems the article only focuses on Docker Compose specifically, probably because it'd be very long otherwise :)
Things like a good security posture by default, health checks, drift detection, and port forwarding.
[1]: https://du.nkel.dev/blog/2023-12-12_mastodon-docker-rootless...
We previously built a package manager for Kubernetes to abstract it in the simplest way possible `glasskube install app` but we failed because every abstraction needs to follow a "convention over configuration" pattern at some point. Also, we weren't able to monetize a package manager.
With Distr (https://github.com/distr-sh/distr), we have actually been able to help companies not only package but distribute and either manage or give their customers a way to self-manage applications. Our customers are able to land on-premises contracts at enterprises way faster than before, which is also a clear ROI for paying for Distr.
So, I don't think that you can get the flexibility of a distributed application orchestrator with a simple declarative YAML file if your target environments are diverse.
The inputs (values) are yaml so you can make it look exactly like a Docker Compose file if you want (wouldn't be surprised if there's some charts floating around that do that)
* Lack of a user-friendly way of managing a Docker Compose installation on a remote host. SSH-forwarding the docker socket is an option, but needs wrappers and discipline.
* Growing beyond one host (and not switching to something like Kubernetes) would normally mean migrating to Swarm, which is its own can of worms.
* Boilerplate needed to expose your services with TLS
Uncloud [1] fixed all those issues for me and is (mostly) Compose-compatible.
I've been using portainer for years, it's decent.
It's been a great way for us to make sure that developers and CI/CD get exactly the same build environment, mount-points, paths, network access, permissions, etc.
It's been a super solid tool overall, and I'm pretty happy with it. The only thing that would make our setup better would be if we could figure out how to go rootless/daemonless with it.
Could i survive with 10 seconds of downtime, probably, but I'd really like if I could avoid it.
https://uncloud.run/docs/guides/deployments/rolling-deployme...
They've basically lost the war against Kubernetes but they could easily claim a lot of ground when it's just one more tweak you're adding to your docker-compose file as it scales.
if you use Caddy as your reverse proxy (instead of nginx for example which does not do this), when requests come in and your service is missing because it's being deployed, Caddy waits for a timeout before giving up. this means that visitors during the brief deploy period don't see errors - they just get a slightly longer wait, which often is not obvious depending on how long your service takes to boot.
Yes, you can deploy a Go binary easily with systemd. Could you reliably do this across a fleet of machines? Including managing its configuration, persistent storage, database, network setup, etc.? Maybe, just need Ansible or equivalent config management. What if it were multiple Go binaries? And what if some of them needed to scale up some days because they hit more traffic than the others?
And on and on. Yes, not everyone needs Kubernetes, Nomad or other advanced orchestrators. But comparing them to running a Go binary with systemd is an unfair comparison.
https://docs.podman.io/en/latest/markdown/podman-systemd.uni...
Service file lives in the mono repo where all 6 services live.
Makes it trivial to make changes and redeploy.
If you run more than one service/codebase, you might be better suited to using a proper container orchestration platform. Doesn't have to be Kubernetes. AWS ECS, GCP Cloud Run, Kamal are all modern options here.
If you run a single codebase in production, why are you even containerizing? Language ecosystems have done a phenomenal job of improving their dependency management since Docker was released. Python has uv. Go has modules. NodeJS has pnpm. Do you actually get benefit from containerizing if you're deploying to a single production host somewhere?
This section misses the one thing I was interested in: how do you avoid downtime in a deployment?
I like to write web applications with Perl and Mojolicious, and a deployment is just "hypnotoad app", and then hypnotoad gracefully starts up new worker processes to handle new requests and lets the other ones exit once they've finished handling their in-flight requests.
When I switched to Docker I found that there was no good way to handle this.
edit: thanks to next comment for referencing one
Seems like an ad for whatever "Distr" is though; I haven't run into any of these issues with Dokploy and everything's been running fine for months.
> This is the shape Distr lands on
I ran docker compose in development a lot. Just an easy way to turn on / off 5 different services at once for a project. Over time this was filling up my machine's storage (like 1 TB). Every few months I needed to run docker compose prune and see 600GB free up
https://docs.docker.com/engine/logging/drivers/journald/
I believe Podman can do something similar.
Is using Docker/Compose "just" as the layer for installing & managing runtime environment and services correct? Especially for languages like PHP?
I.e. am I holding it wrong if I run my "build" processes (npm, composer, etc) on the server at deploy time same as I would without containers? In that sense Docker Composer becomes more like Ansible for me - the tool I use to build the environment, not the entire app.
For the purpose of my question, let's assume I'm building normal CRUD services that can go a little tall or a little wide on servers without caring about hyper scale.
It's perfectly fine, as long as you accept the risks and downsides. Your IP can get ratelimited for Docker Hub. The build process can exhaust resources on the host. Your server probably needs access to internal dev dependencies repository, thus, needs credentials it would not need otherwise. Many small things like that. The advantage is simplicity, and it's often worth the risk.
How? What I'm describing is using Docker less.
> The build process can exhaust resources on the host
Maybe, but I've yet to have a host where that's the case for usual CRUD fare.
> The advantage is simplicity, and it's often worth the risk.
That's basically what I'm evaluating for here.
For bog standard LAMP or similar stack applications, I've not understood the advantage of going through the build-image-then-pull-on-host rigmarole. There's more layers involved there than something like provisioning with Ansible and just having a deploy script to run the usual suspects.
But I have seen that done fairly often, hence was wondering what the point was.
Also adds moving parts to your deploy which increases risk/introduces more failure modes.
Couple things that come to mind
- disk space exhaustion during build
- I/o exhaustion esp with package managers that have lots of small files (npm)
However, on the small/hobby end I don't think it's a huge concern.
> disk, i/o exhaustion
This is why I mentioned specifically for ecosystems like PHP, which are interpreted. I'm specifically asking for that use case.
I'm not building binaries, my "build" steps are actually deployment steps (npm build, composer install, etc) that I'd be running in exactly the same way on the host. The image I'm deploying by definition also contains my source code because I'm not deploying anything compiled.
That's what I answered for.
>I'm not building binaries
If you were, I would have added CPU to the list.
>my "build" steps are actually deployment steps (npm build, composer install, etc)
No, those are build steps. If you weren't using Docker, you would either run all those and shove in a zip/tarball or package into a deb/rpm, etc
>The image I'm deploying by definition also contains my source code
It doesn't contain .git or need credentials to your git/SCM
>I'm not seeing the benefit of the whole "build image, pull on server" pipeline when I can just ditch the registry and added layers by doing those steps on the server as I would normally in other kinds of scenarios
You don't need a registry--you can Docker save/load to push images directly to the server. Images buy you a versioned artifact with all the code-level dependencies baked in. Some maintainer yanks their package from npm? Who cares--you have a copy in your Docker image. Your new app version doesn't work? Edit 1 line to point back to the old image tag and rollback.
>> The build process can exhaust resources on the host
>Maybe, but I've yet to have a host where that's the case for usual CRUD fare.
When the build process completes, it tears down the overlayfs which causes everything to sync which leads to a big I/O spike. Depending on the server and amount of files, it might have no impact. However, I've seen build servers become completely unresponsive for 5+ minutes due to the I/O load when this happens. One place I worked, we had to switch our build servers to NVMe--the Docker container teardown caused spikes over 100k IOPs. Can't remember the exact details--it was React either React web front end or React Native mobile app.
>There's more layers involved there than something like provisioning with Ansible and just having a deploy script to run the usual suspects.
`docker save myimage:tag | gzip | ssh user@server 'gunzip | docker load'`
Not saying creating distributable artifacts is the de-facto answer, but I'd strongly consider whether it's really that much more complicated.
"Builds" are the same as deploys, so when working with server(s) instead of larger scale deployments, I'm not seeing the benefit of the whole "build image, pull on server" pipeline when I can just ditch the registry and added layers by doing those steps on the server as I would normally in other kinds of scenarios.
But I have seen this in action, which is why I'm wondering if I'm missing something.
The clearer benefit to me seems to be in this scenario to use it as a fast environment provisioning tool.
If you want to test something that is between compose and k8s, check ring: https://github.com/kemeter/ring
And the error handling was terrible. Most of these problems resulted in a Python stack trace in some docker-compose internals instead of a readable error message. Googling the stack trace usually lead to a description of the actual problem, but that's really not something that inspires confidence.
Very few separate ecosystem transfers are quite that frictionless.
Docker compose assumes all your services can reach each other over docker, which I find horribly insecure.
I separate all my services by user account at least, maybe even by VM, and I run them all in rootless podman containers. So it just doesn't fit my style, but I'm sure it works fine.
The only issue is the little downtime during deployments.
Granted, its B2B Saas with not many users, maybe 100 concurrent.
80% of workloads dont need the complexity of Kubernetes and run fine with compose.
Super easy deployment of additional apps, defined completely in one file (incl setup on host, backups, reverse proxy config, etc).
Never found a reason to migrate away. Swarm was already considered dead when I started using it in 2022[1], but the investment was so low and benefits so big, that it was the right choice for me. I think a lot of people are replicating swarm features with compose, losing a lot of time. But hey, to each their own choice!
Using traefik or caddy as proxy.
Docker context for remote access - over Internet or vpn, whatever.
Swarm-cronjob for scheduled things.
Labels for things that need to run in particular places.
So easy.
Personally, k8s is fine, but its an abstraction for building a service architecture, not the thing an end user (developer) should ever use. If you are in a big company and you are using helm or k8s yaml files to roll things out, your infra or platform teams have missed something out.. building the platform!
https://developer.hashicorp.com/nomad
Disclaimer: I used to work for HashiCorp
even their follow up - Docker Compose vs Kubernetes.
Docker compose for me has been great - no complexity.
Docker/containers are great, especially for local development. But I feel the docker compose model quickly becomes a lot of messy brittle squeeze for little gain when multiple containers need to integrate.
Better then to just take the plunge for the "real deal" and set up a non-HA k8s/k3s cluster with the interactions between the workloads clearly specified.
In other words. I care care more about the interactions declaratively spelled out than the "scale to the moon" HA, auto-scaling, replicas or whatever people get sold on.
And LLMs make this even easier. If you love reviewing yaml manifests....
It's nice to get an easy question every once in a while.
Ie you need a sysadmin. Oops, you fired them all 10 years ago when the agile devopsing became the best thing after the pumpkin latte.