Docker Compose was production ready in 2015 and it still is today. I've lost track of how many projects I've deployed with it and never really ran into a single issue where Docker Compose was at fault. It's super solid.
I love Docker Compose. It is simple to use, easy to organize and manage, and very robust. Also, our company does not need to "scale" production aggressively. Our production load is very predictable, so Docker Compose fits like a glove.
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!
It's simple to use only for toy use cases, that's why nobody uses it. The article everyone in this thread seems to like only goes as far as 'I pushed to git so it must be ok' which is laughable and I'm not even DevOps.
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.
I think people are using different meanings of “production environment.”
I agree with gear54us and upvoted their comment, but I also understand what the author of the root comment is saying.
I have also delivered systems using Docker Compose that are actually running in production. The point I want to make is that people may define “production” differently depending on the number of active users, operational requirements, and risk level.
To me, this debate feels similar to the broader monolith vs. microservices debate.
The post receive hook provides you real-time feedback as it runs in the terminal where you did the git push. If something broke during the deployment you'd get notified by looking at the output. If it's running in CI, you'd see a CI failure and get notified using whatever existing mechanisms you have in place to get notified of deployment pipeline failures.
Zero downtime server upgrades are easy. You could make a new server, ensure it's working in private and then adjust DNS or your floating IP address to point to the new server when you're happy. I've done this pattern hundreds of times over the years for doing system upgrades without interruption and safely. The only requirement is your servers are stateless but that's a good pattern in general.
Really liked reading your blog. Bookmarked for future. One question: for databases, do you recommend using containers as well because in development, I love the ease of using databases in docker compose as well but I always worry about production in terms of resilience. Thoughts ?
For databases, I usually host them on a separate server. This could either be through Docker Compose or a managed DB server. If a managed DB is affordable enough I'd reach for it.
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.
At this stage the volume/persistence configuration for all of the major DBs is arguably extremely well understood and has been for years. The only real risk in running the DB as a container for most people is not configuring volumes for persistence correctly.
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!
Thank you. I had been procrastinating on learning how to work with containers and finally got a handle on Docker Compose to play with self-hosting a coding agent and was worried that I'd once again procrastinated so long that I'd picked something up long after it was already dead.
Should you have a turkey sandwich for lunch in 2026? I don't know buddy just do whatever. There are ten thousand other sandwiches you could eat surely, but does turkey sound good for you?
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?
So we agree people shouldn't write off these posts with "does turkey sandwich sounds good to you" like it's some deep insight to default to the trivial answer?
The user plausibly isn't asking others to tell him what he "likes" -- a subjective preference -- but rather the user could be asking for objective research and information.
The analogy you are replying to is pointing out that it works just fine for production, so if you should use it or not is simply a matter of taste.
More to the point, there is no objectively right answer of what stack you should use. There are plenty of objectively wrong answers, but compose isn't one of them.
Even the wrong answers, it comes down to who's dealing with the mess? Who's paying for the site's uptime? Who isn't getting paid while the site isn't up?
But then if you're not going to answer a question on technology, and you won't motivate any of the choices, including the one to not give an answer, what's the value in participating to the conversation?
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.
They are not using Docker at runtime for their services. Every company uses Docker for builds unless they have a particular cost or ethos they're avoiding and purely using Linux/podman/buildah/et al.
Thinking about it a little further, though, I believe Rancher Desktop has come a long way and may be eating market share.
There are more secure alternatives. Are you sure those you listed actually use it on the servers? I would guess that at least Spotify and Netflix uses some other container runtime than Docker on their production servers.
For a long time Docker was helpful and opened exposed ports on the firewall. So you wanted to access your redis ports locally and exposed it on the container? Now everything in there is accessible on the open internet.
I believe they've fixed it but I haven't used Docker in years so I wouldn't know.
How do you guys, who run Docker in production deal with managing nftables firewall on hosts running containers? By design docker daemon creates and manages a set of firewall rules to forward traffic between containers and ingress traffic into containers as well as masquarades the outgoing container traffic. That is all well until admin needs to alter hosts firewall to allow and deny other traffic unrelated to docker - and restarting nftables or even applying new nftables rules usually ( flush ruleset in /etc/nftables.conf ) purges all the docker created rules and effectively breaks everything until docker daemon is restarted and rules re-created. I have partially solved this by using nftables filter chains with different names - admin_input/admin_output and using input hook with negative priority - so that traffic I choose to block is evaluated before docker rules are applied - that feels a bit like hack, but so far is the only way I have found. It is good practice in this day and age to run local firewalls on all hosts with policy deny, so that only traffic explicitly allowed can pass, that can severely limit blast radius during compromise.
My containers run in dedicated "docker host" VMs. And I never expose ports on 0.0.0.0, just the private internal IP. Most (all) of my docker hosts do not have a public IP anyway. I use wireguard to access them myself. If they need to be public I reverse proxy with caddy from my web server (or use Authentik's embedded proxy). These servers have access to the same private LAN which could be hardened without having the issues you brought up.
By the way most docker based implementations do not actually need the userland proxy docker runs automatically. Disable it in /etc/docker/daemon.js
I reverse proxy everything through a Caddy instance running on the same machine so I avoid the firewall dance entirely by just prefixing all my port assignments in the compose file with the loopback IP (eg. 127.0.0.1:3000:3000). Nftables denies all but 80 and 443 and I don't have to worry about restarts/flushes breaking things.
This is how I self host all my home services (Home Assistant, PFSense, Frigate etc), I do not for the life of me understand why so many folks doing self-hosted services for themselves put them on the public internet.
Caddy will even do fully automated valid TLS certificates for private IP ranges via DNS ACME challenge for free etc with renewals handled, so all my internal self-hosted sites have properly terminated TLS too, accessible by connected VPN clients.
It's funny that for many of us in our day job, we stand up private services behind a VPN all the time so only work clients can access it, but when self hosting don't bother with a simple wireguard/tailscale config etc.
This is surely the easiest and I would guess the safest way, and has the added benefit that your proxy (nginx in my case) can handle SSL for you, making certificate deployment a breeze.
I put a firewall ahead of the Docker host so that they aren't running on the same system. Docker can do what it wants to on the host without stepping on my firewall rules.
Well, as an example we usually set incoming rules to filter SSH only from administrator IP addresses, TCP 10050 only from zabbix monitoring server and leave few icmp types required and rest is dropped and logged.
For forward chain we set docker network ranges to route between themselves and only services actually used in containers. Allow container outgoing connections to our DNS servers, centralized HTTP proxy server and monitoring - nothing else containers are allowed to route to.
And for output is similar, only allow our DNS servers, NTP, HTTP proxy, centralized rsyslog where everything goes and zabbix monitoring server and a few icmp types - nothing else gets out and is logged.
With the advent of these supply chain attacks we read about often here it's just a matter of time some container is compromised and this seems like only viable way to at least somehow limit impact when such an event occurs.
To expand, you can use privileged containers, host network, capabilities, etc if the software really needs it. In that case, Docker basically becomes an init system/service manager but you get a singular daemon managing everything
I think many of these issues are also solved by Podman and systemd depending on what kind of "production" you're building for. If you're building a linux-y appliance and you need to run a few containers I think Podman is a much better and more ergonomic way of doing so. I think perhaps that's less true for running a web service (where the linux environment is just a means to that end).
GP is talking about podman with generated systemd unit files (a.k.a. podman quadlet[0]), not the docker-compose-compatible podman-compose ...and I'd agree, systemd can manage services on a system just fine, and even better than any compose workload ever could.
journald will help with logs, and the pull policy[1] helps with mutable tags. What help do you need with "orphan containers"?
I'm not OP, but the whole podman compose topic gets quite confusing, as initially Podman didn't seem to know what they were trying to do. I've given some more context around it in previous comments.
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`.
One of the nastiest aspects of migrating from docker to podman really is "what to do about docker compose?" coz there are three wildly divergent ways to answer that all of which really suck under certain specific circumstances.
Im no fan of docker and podman by itself is a step up but orchestration headaches are enough to ruin that.
I don't understand what you're asking here. The answer to that is probably nothing. That is unless you want:
- systemd to manage your containers
- You want to use K8s primitives (which are mostly compatible)
I'm unsure what the 3rd method is you're talking about. The nice thing about Podman's compose API is you don't have to change anything (mostly). You can point all your docker tooling to Podman's socket, and it'll (mostly) magically work.
This is what stopped me from picking up Podman more, all our devs use Docker and have been writing compose files for years now. When the response at the time was "you're using Podman wrong, Quadlets are the hot stuff now" it just felt like too big a risk and commitment to jump to at the time. Have things settled more? Getting away from Docker is a bigger priority nowadays for us.
Docker (Compose) has some quirks compared to Podman (Compose), e.g. when using gvisor or a lot of internal networks. Depending on what you do your milage will vary, though.
Agreed. I found compose overlay files merged list values differently between the Docker and Podman versions, which was a PITA in teams running Docker & Linux dev machines.
Then you learn podman can't even list containers for all users properly and it kind of starts smelling like the whole ip4 vs ip6 debacle: bunch of vocal proponents wanting you to subject yourself to endless torture for no discernible reason.
I desperately WANT to like podman quadlets and keep trying to find a use case for them. But I always got the impression that the developers who implemented quadlets never actually had to manage multiple containers in a real production environment.
Having your whole application with its containers, volumes, and networks all defined together in one easy-to-read YAML file is a way better experience. Deployment is two steps: 1. `git clone foo` 2. `docker compose up -d`. You can see the state of the application containers with `docker compose ps`. You can run multiple compose applications on the same host and manage them separately by putting them in different directories.
With quadlets, you delegate everything to systemd. You have to break the configuration up into a bunch of tiny unit files and then separately copy them to /etc or a dedicated user's dotfiles. An application with a handful of containers and multiple networks/volumes/etc can spiral into a dozen unit files. Good luck SSH'ing into an unfamiliar system and understanding at a glance what it's doing. It is far more annoying to predictably deploy and tightly couples your application configuration to the host system configuration. (Even moreso if you created dedicated users for each application, which I understand is the recommended solution.)
If I'm just holding it wrong and there exists some better tooling to manage podman in prod that I don't know about, I'm happy to hear about it.
Honestly with the hell I'm going through just trying to get Podman to run properly on macos, I can't imagine trusting the Podman people with a production deployment. I was not particularly impressed with the Docker tooling, but Podman has been even worse. This is a not-remotely exhaustive list of things I've run into in the last 24 hours:
* Podman fails to build a 16GB container image (after 30 minutes of downloading dependencies) despite having 90GB free out of a 200GB podman virtual machine
* Podman machine will, for reasons I don't understand, create a filesystem in a block device with wildly different sizes, and it seems like it's just random
* Pushing podman images to a container image registry via the Podman Desktop UI gives no indication that it's doing anything or even recognized the "push image" click, a success or error notification _might_ appear several or tens of minutes later or possibly not at all
* Starting a podman machine might work, but it fails ~75% of the time with not-particularly-exotic options (a bunch of ram and disk) and very cryptic error messages, frequently telling me to file bug tickets (I have)
* Podman Desktop won't let me create a podman machine with more than 44GB of disk, but the podman machine CLI won't let me create a machine with fewer than 100GB (IIRC--it's some number larger than 44, in any case)
Apart from the container image being absurdly large (Python developers love massive packages, I guess), I'm not doing anything exotic.
Is there a nice guide for podman that includes quadlets (or saying not to use them?) I find lots of guides stray into things that work on redhat, and on my Linuxes of choice, Raspbian and Ubuntu, things aren't straightforward.
Thanks - you're right. I just have never known whether to quadlet or not to quadlet, as the centre of gravity seems to be moving that way, but that might be a redhat-first feature.
Can't comment on Raspbian, but Ubuntu LTS (has/had) a seriously outdated podman version. This is the kind of nuisance the Debian derivatives have been running into for more than 20 years: they are extremely conservative, and if that is all you need, then that is great, but if not, you'll have to either run the latest Ubuntu (not LTS), or you upgrade to something like fedora.
> they are extremely conservative, and if that is all you need, then that is great
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.
I like running docker compose for my simple needs because it consolidates pretty much all the config in one declarative file, and docker manages 'everything'.
By now I know how to handle the handful of caveats listed in this article. Beyond what's listed there, I'd also give a mention to the way port publishing works (the fact that it ignores firewalls), as that's something that still trips people up if they don't know about it.
> 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.
> 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.
> I'd also give a mention to the way port publishing works (the fact that it ignores firewalls), as that's something that still trips people up if they don't know about it.
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 :)
I really want something that is Docker Compose but for Kubernetes. I mean that I can have a simple way to declaring resources in just like Docker Compose, but I run the environment in Kubernetes so that I can get to test the behaviors when there are multiple copies of the softwares running together. I do rely on Kubernetes heavily for distributed and networked software deployment, so it is even better if we can emulate things like latency or burstable packet loss so that we can do a controlled chaos test for reliability test. I tried Skaffold, Tilt, Devspaces and Devpod/Coder v2, none of them are really simple like Compose.
This is a problem we, as a company, have thought about a lot, but we always concluded that Kubernetes is already the simplest abstraction of a distributed system that is feasible for the diverse needs that the biggest companies out there have.
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.
Same, I've tried three or four times to make it work, including one attempt that just translated compose.yaml into k8s yaml, and every time I came away thinking, "just use k8s". K8s yaml looks complex, and can start to feel very boilerplate, but attempts to hide the complexity often just lead to something not-flexible-enough because it encodes convention over configuration, and inevitably some project runs into limitations and pretty soon you've just built an abstraction layer that leaks or is equally complex/verbose and now you have to learn something new.
Just use k8s and follow similar patterns is the conclusion I've arrived at personally.
Helm mostly does that. Not a huge fan of a text templating engine generating yaml but once you get your chart setup with a few variable inputs, you can continue using it for a bunch of other stuff with minimal new config.
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)
I've recently been dipping my toes into k8s / kustomize / helm, and I recently had a situation where having a base deployment yml template that I wanted to reuse across various deployments. I had a look at Helm and I was frankly shocked how bad the templating was with Go templates, it was close to unreadable and felt very brittle!
Yeah, that's fair. I don't think it's as bad if you make your own charts and can more liberally hardcode things. Community charts tend to have an insane amount of "knobs" so you can basically change everything being templated.
I don't know if I'd necessarily call it brittle, though. You can use `helm template` and various linters to validate the generated yaml is correct (and use something like pre-commit to autorun)
How so? I find it much the same to other templating engines like Jinja, though I'm definitely not a fan of the syntax. But that hardly matters anymore with LLMs.
I think it's a combination of text templating + yaml (which is whitespace delimited). When templating html with Jinja, it's not a big deal if the indentation isn't right. In helm, best case you get a syntax error, worst case you end up overwriting keys and producing something syntactically but not logically correct.
I did that too, and ended up just skipping helm and using envsubst to interpolate the values I need at runtime from env vars. Nearly everyone preferred that approach. YMMV of course.
I think that's a good approach if it works for your use case. Sometimes you might want something slightly more sophisticated like basic logic (loops/conditionals). In those cases, you can still use helm but you have an extremely simple template and avoid many of the "can't read this template" helm pitfalls.
Compose is great, but a couple things always created friction for me when using it for non-local setups:
* 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.
For remote installation, use the `docker context` command. You create a context with a named SSH host and then it connects via SSH to that host (as configured in your local ssh_config) and uses its docker daemon. Everything works flawlessly apart from local bind mounts (for obvious reasons).
If you remember `docker machine`, this is basically the modern version of that.
I am using systemd + go binary deploy. Running 10 years+ in production. Meanwhile docker based setup fail every now and then. And kubernetes? well forget about it.
I mean no disrespect. This is more of a rant at how things are today. It is telling that over-complicated solutions have become so common that, for the current generation of devs, Kubernetes is the obvious way of doing stuff and a simple systemd service is the obscure one. I am sure there are good reasons for this, but it still feels like a loss when simplicity is no longer obvious.
That's like comparing you being able to make a salad from tomatoes and an industrial tomato sauce making facility. Both take tomatoes and end up with food, but the scale is completely different.
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.
If you love docker compose then you would love k3s. A single server with k3s is basically docker compose + the possibility to use helm to install all kinds of open source project such as monitoring and it just works.
I am using docker-compose everywhere. I really enjoy using it. I have a single thing that is annoying for normal production deployments, and that is that it isn't super easy to have a rolling deployment, I just need two replicas for zero downtime deployment, and I don't really want docker swarm. I think it is the networking which breaks at that point, and you have to have a more involved setup, and at that point I'd just use kubernetes, as I know how that works.
Could i survive with 10 seconds of downtime, probably, but I'd really like if I could avoid it.
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.
Record the existing container id, rescale the service to 2 instances (hence bringing a second container up), wait for the second one to be healthy, (optional) stop directing traffic to the old container, wait a few seconds, stop the old container, rescale the service back to 1 instance.
To be honest I never really understood the benefit of Docker (Compose) Secrets - which is different from Swarm Secrets. Imho there just plain host mounted volumes, which are hidden from inspect commands?
I prefer Portainer to manage my docker composes. It is simple and can do it all instead of using cli.
Added benefit if you have multiple hosts and want to manage them from one place. And you can extend the whole setup with git for version control.
Kubernetes sounds like overkill, but I've been running microk8s for few standalone servers. This feels a pretty good match when working with agents. Codex can manage the cluster also over ssh, schedule new pods, check statuses, logs etc.
I think k8s is a great choice today specially when you can plug it into Gitlab and have a control plane for your clusters in the same place where your code lives.
I do this via Dokploy on a hosted Linode VPS and absolutely love it. Super easy to set up and maintain for tons of little side projects that don't require tons of resources.
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.
That's just different customer personas for marketing reasons, just like Vercel has "Build and deploy on the AI Cloud" as their main tag line on the landing page. It doesn't mean they are an "AI company".
I quite like the “shape” term; every type of (sigh…) stakeholder … understands it, and I don’t need to swap in terms like “interface”, api, contract, architecture, structure, etc - unless I want to talk specifically about that thing. Everyone can fit a triangle and parallelogram in their mind, which is just dandy when I’m just trying to communicate difference.
> Every docker compose pull keeps the previous image on disk. Every container with the default json-file log driver writes unbounded JSON to /var/lib/docker/containers/<id>/<id>-json.log. On a busy host this is one of the most common reasons for an outage: the disk fills and Docker stops being able to write anything
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
A very simple fix for that is to use the systemd log driver to send all the container logs to journald. Then you can set a size or time limit on journald.
Sure, it's stable enough, just keep in mind you won't get any autoscaling (or manual for that matter). Swarm is still supported by a third party, but that party has been loudly signaling that they intend to kill it off this year or next. Kubernetes isn't too big a leap, but damn are all those yaml manifests annoying to maintain. I usually just copy and tweak them from another project.
Somewhat adjacent in how I look at using Docker at all in prod, here's what I always wonder:
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.
> if I run my "build" processes (npm, composer, etc) on the server at deploy time
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.
I would say it's bad practice because you end up having to copy all the build dependencies (source code) to the host and you're potentially putting a bunch of extra load on the host during the build process.
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.
Have a look at multi stage container builds. Your images should not need a build step at start, the result should be in the baked image. Else you become reliant on fetching packages during build etc.
One big thing I think is whether you want some sort of non-trivial network configuration, such as multiple external IPs via ipvlan. That's technically possible off docker, but not in a responsible way as anything in the ipvlan will be accessible to the public internet. Overall the implementations for this are very janky and occasionally enter tilted states that are close to impossible to recover from short of a restart of the docker daemon.
Sure why not, it just never fits into my model of how I design infrastructure.
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.
My experience with docker-compose is a bit outdated, but my impression some years ago was that it was too sensitive and fragile. I encountered bugs or incompatibilities that broke the docker-compose setup often enough to be forced to pin the specific docker and docker-compose versions.
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.
I am doing just this. Running docker compose on a server. When there will be to many microservices, we will move them in managed Kubernetes on a cloud platform or Nomad if any cloud platform offers it.
Personally I have moved to k3s, although after learning a bit too deep how k8s operates when writing custom controllers at the day job.
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....
Docker Compose was production ready in 2015 and it still is today. I've lost track of how many projects I've deployed with it and never really ran into a single issue where Docker Compose was at fault. It's super solid.
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.
I love Docker Compose. It is simple to use, easy to organize and manage, and very robust. Also, our company does not need to "scale" production aggressively. Our production load is very predictable, so Docker Compose fits like a glove.
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!
"It is simple to use, easy to organize and manage, and very robust."
This is why nobody uses it. Cloud stuff has to be as baroque as possible.
It's simple to use only for toy use cases, that's why nobody uses it. The article everyone in this thread seems to like only goes as far as 'I pushed to git so it must be ok' which is laughable and I'm not even DevOps.
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.
I think people are using different meanings of “production environment.”
I agree with gear54us and upvoted their comment, but I also understand what the author of the root comment is saying.
I have also delivered systems using Docker Compose that are actually running in production. The point I want to make is that people may define “production” differently depending on the number of active users, operational requirements, and risk level.
To me, this debate feels similar to the broader monolith vs. microservices debate.
The post receive hook provides you real-time feedback as it runs in the terminal where you did the git push. If something broke during the deployment you'd get notified by looking at the output. If it's running in CI, you'd see a CI failure and get notified using whatever existing mechanisms you have in place to get notified of deployment pipeline failures.
Zero downtime server upgrades are easy. You could make a new server, ensure it's working in private and then adjust DNS or your floating IP address to point to the new server when you're happy. I've done this pattern hundreds of times over the years for doing system upgrades without interruption and safely. The only requirement is your servers are stateless but that's a good pattern in general.
Really liked reading your blog. Bookmarked for future. One question: for databases, do you recommend using containers as well because in development, I love the ease of using databases in docker compose as well but I always worry about production in terms of resilience. Thoughts ?
For databases, I usually host them on a separate server. This could either be through Docker Compose or a managed DB server. If a managed DB is affordable enough I'd reach for it.
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.
At this stage the volume/persistence configuration for all of the major DBs is arguably extremely well understood and has been for years. The only real risk in running the DB as a container for most people is not configuring volumes for persistence correctly.
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!
What effect does docker compose have on database resilience?
Hey, just want to say thank you for your blog. I've learned so much about Docker, dev tooling, and engineering from reading it over the past 7 years.
Thanks a lot, I really appreciate it.
Have you read the article?
Thank you. I had been procrastinating on learning how to work with containers and finally got a handle on Docker Compose to play with self-hosting a coding agent and was worried that I'd once again procrastinated so long that I'd picked something up long after it was already dead.
It makes things amazingly simple and portable, dealing with things otherwise seems so horrible now.
Should you have a turkey sandwich for lunch in 2026? I don't know buddy just do whatever. There are ten thousand other sandwiches you could eat surely, but does turkey sound good for you?
> does turkey sound good for you?
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?
Then you either need to try it or research it. Other people can't tell you if you like turkey or not.
So we agree people shouldn't write off these posts with "does turkey sandwich sounds good to you" like it's some deep insight to default to the trivial answer?
It's not a deep insight. Neither is the title of the article. It tells the reader nothing and makes the author sound like they have nothing to say.
Does the author have nothing to say or is this just an empty critique of the authors taste for titles?
Because if that's the case, I also don't care for baroque music that much.
Does a turkey sandwich sound good to you?
Yes: okay try it
No: okay you don't have to try it
Unsure: okay you can read about it some more and decide if it sounds good to you
Are we still talking about infrastructure?
Does asking people on HN count as "research"? It does in my book. It's not perfect, but then nothing is.
The user plausibly isn't asking others to tell him what he "likes" -- a subjective preference -- but rather the user could be asking for objective research and information.
Surely we can give more informed answers to questions like this than to compare a technical decision to the craving for a sandwich.
Comments like this are apathetic and reduce the challenges of good software engineering to hopes and random chance.
The analogy you are replying to is pointing out that it works just fine for production, so if you should use it or not is simply a matter of taste.
More to the point, there is no objectively right answer of what stack you should use. There are plenty of objectively wrong answers, but compose isn't one of them.
Even the wrong answers, it comes down to who's dealing with the mess? Who's paying for the site's uptime? Who isn't getting paid while the site isn't up?
If you already understand it all in-depth, anything becomes "are the benefits worth the trade-offs" but it's useless advice for the people who don't
Caffeine is healthy. Caffeine is unhealthy. Caffeine is healthy again. Eggs are healthy. Eggs are unhealthy. Etc etc etc.
There’s a reason articles like this exist. Things change.
Well, the fashion changes. At one time, sugar too was advertised as beneficial for tooth health.
What does this have to do with the article?
Is your point that we shouldn't motivate our technological choices? I wouldn't use Docker Compose in production.
I also would not eat a turkey sandwich for lunch on a Tuesday.
*shudder*
Yes. I clearly believe we should not motivate choices in technology.
But then if you're not going to answer a question on technology, and you won't motivate any of the choices, including the one to not give an answer, what's the value in participating to the conversation?
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.
I note the absence of a motivation.
I wouldn't use Docker in production.
I assume you mean Docker _daemon_
Docker also commonly refers to Docker _images_ or Docker-esque container setups
Spotify, Visa, NASA, Netflix, and a zillion others do, why not?
They are not using Docker at runtime for their services. Every company uses Docker for builds unless they have a particular cost or ethos they're avoiding and purely using Linux/podman/buildah/et al.
Thinking about it a little further, though, I believe Rancher Desktop has come a long way and may be eating market share.
I just never felt the need for it. I'm not saying it's bad (I really don't know), it's just one of these things that solve problems I don't have.
Most companies I’ve seen from the inside were using containerd or crio.
There are more secure alternatives. Are you sure those you listed actually use it on the servers? I would guess that at least Spotify and Netflix uses some other container runtime than Docker on their production servers.
For a long time Docker was helpful and opened exposed ports on the firewall. So you wanted to access your redis ports locally and exposed it on the container? Now everything in there is accessible on the open internet.
I believe they've fixed it but I haven't used Docker in years so I wouldn't know.
SRE here, my thought is "Sure, Docker Compose is great for production assuming your needs are light and Docker Compose works well for you."
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...
How do you guys, who run Docker in production deal with managing nftables firewall on hosts running containers? By design docker daemon creates and manages a set of firewall rules to forward traffic between containers and ingress traffic into containers as well as masquarades the outgoing container traffic. That is all well until admin needs to alter hosts firewall to allow and deny other traffic unrelated to docker - and restarting nftables or even applying new nftables rules usually ( flush ruleset in /etc/nftables.conf ) purges all the docker created rules and effectively breaks everything until docker daemon is restarted and rules re-created. I have partially solved this by using nftables filter chains with different names - admin_input/admin_output and using input hook with negative priority - so that traffic I choose to block is evaluated before docker rules are applied - that feels a bit like hack, but so far is the only way I have found. It is good practice in this day and age to run local firewalls on all hosts with policy deny, so that only traffic explicitly allowed can pass, that can severely limit blast radius during compromise.
My containers run in dedicated "docker host" VMs. And I never expose ports on 0.0.0.0, just the private internal IP. Most (all) of my docker hosts do not have a public IP anyway. I use wireguard to access them myself. If they need to be public I reverse proxy with caddy from my web server (or use Authentik's embedded proxy). These servers have access to the same private LAN which could be hardened without having the issues you brought up.
By the way most docker based implementations do not actually need the userland proxy docker runs automatically. Disable it in /etc/docker/daemon.js
{
What would the config look like if I have my docker containers split up over multiple VMs?
This is the way, ended up using identical setup.
Could you elaborate on your setup? Is the docker host also your web server on which you run caddy?
I reverse proxy everything through a Caddy instance running on the same machine so I avoid the firewall dance entirely by just prefixing all my port assignments in the compose file with the loopback IP (eg. 127.0.0.1:3000:3000). Nftables denies all but 80 and 443 and I don't have to worry about restarts/flushes breaking things.
A really nifty thing is that you can also of course bind this to the device's tailscale ip!
Also you don't even need the loopback address if the traffic is between one container and another, just a bridge network is fine.
This is how I self host all my home services (Home Assistant, PFSense, Frigate etc), I do not for the life of me understand why so many folks doing self-hosted services for themselves put them on the public internet.
Caddy will even do fully automated valid TLS certificates for private IP ranges via DNS ACME challenge for free etc with renewals handled, so all my internal self-hosted sites have properly terminated TLS too, accessible by connected VPN clients.
It's funny that for many of us in our day job, we stand up private services behind a VPN all the time so only work clients can access it, but when self hosting don't bother with a simple wireguard/tailscale config etc.
This is surely the easiest and I would guess the safest way, and has the added benefit that your proxy (nginx in my case) can handle SSL for you, making certificate deployment a breeze.
firewalld supports docker and handles all of its routing/changes. I've standardized on using it in my environment.
I put a firewall ahead of the Docker host so that they aren't running on the same system. Docker can do what it wants to on the host without stepping on my firewall rules.
It makes sense but that's more overhead and the spirit of the post seems to be "can we just docker compose and be done with it?"
I use UFW, and this config: github.com/chaifeng/ufw-docker
The only modification is that I pin containers to an IPv4 address so I can limit the forward rule to that address.
I don't. I'd run other workloads on separate hosts
On my docker hosts there is no other traffic unrelated to docker. Everything goes in containers.
Well, as an example we usually set incoming rules to filter SSH only from administrator IP addresses, TCP 10050 only from zabbix monitoring server and leave few icmp types required and rest is dropped and logged.
For forward chain we set docker network ranges to route between themselves and only services actually used in containers. Allow container outgoing connections to our DNS servers, centralized HTTP proxy server and monitoring - nothing else containers are allowed to route to.
And for output is similar, only allow our DNS servers, NTP, HTTP proxy, centralized rsyslog where everything goes and zabbix monitoring server and a few icmp types - nothing else gets out and is logged.
With the advent of these supply chain attacks we read about often here it's just a matter of time some container is compromised and this seems like only viable way to at least somehow limit impact when such an event occurs.
To expand, you can use privileged containers, host network, capabilities, etc if the software really needs it. In that case, Docker basically becomes an init system/service manager but you get a singular daemon managing everything
I think many of these issues are also solved by Podman and systemd depending on what kind of "production" you're building for. If you're building a linux-y appliance and you need to run a few containers I think Podman is a much better and more ergonomic way of doing so. I think perhaps that's less true for running a web service (where the linux environment is just a means to that end).
What are the benefits of running Podman Compose instead of Docker Compose? I don't see how it helps with orphan containers, logs and mutable tags.
GP is talking about podman with generated systemd unit files (a.k.a. podman quadlet[0]), not the docker-compose-compatible podman-compose ...and I'd agree, systemd can manage services on a system just fine, and even better than any compose workload ever could.
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....
I'm not OP, but the whole podman compose topic gets quite confusing, as initially Podman didn't seem to know what they were trying to do. I've given some more context around it in previous comments.
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`.
One of the nastiest aspects of migrating from docker to podman really is "what to do about docker compose?" coz there are three wildly divergent ways to answer that all of which really suck under certain specific circumstances.
Im no fan of docker and podman by itself is a step up but orchestration headaches are enough to ruin that.
> "what to do about docker compose?"
I don't understand what you're asking here. The answer to that is probably nothing. That is unless you want:
- systemd to manage your containers - You want to use K8s primitives (which are mostly compatible)
I'm unsure what the 3rd method is you're talking about. The nice thing about Podman's compose API is you don't have to change anything (mostly). You can point all your docker tooling to Podman's socket, and it'll (mostly) magically work.
This is what stopped me from picking up Podman more, all our devs use Docker and have been writing compose files for years now. When the response at the time was "you're using Podman wrong, Quadlets are the hot stuff now" it just felt like too big a risk and commitment to jump to at the time. Have things settled more? Getting away from Docker is a bigger priority nowadays for us.
Docker (Compose) has some quirks compared to Podman (Compose), e.g. when using gvisor or a lot of internal networks. Depending on what you do your milage will vary, though.
Agreed. I found compose overlay files merged list values differently between the Docker and Podman versions, which was a PITA in teams running Docker & Linux dev machines.
Then you learn podman can't even list containers for all users properly and it kind of starts smelling like the whole ip4 vs ip6 debacle: bunch of vocal proponents wanting you to subject yourself to endless torture for no discernible reason.
What do you mean it can't list containers for all users?
I mean, ipv6 is for not runnig out of IP addresses. There is a clear discernible reason.
There are workarounds to make ipv4 work, but they complicate the system and make it more fragile.
I desperately WANT to like podman quadlets and keep trying to find a use case for them. But I always got the impression that the developers who implemented quadlets never actually had to manage multiple containers in a real production environment.
Having your whole application with its containers, volumes, and networks all defined together in one easy-to-read YAML file is a way better experience. Deployment is two steps: 1. `git clone foo` 2. `docker compose up -d`. You can see the state of the application containers with `docker compose ps`. You can run multiple compose applications on the same host and manage them separately by putting them in different directories.
With quadlets, you delegate everything to systemd. You have to break the configuration up into a bunch of tiny unit files and then separately copy them to /etc or a dedicated user's dotfiles. An application with a handful of containers and multiple networks/volumes/etc can spiral into a dozen unit files. Good luck SSH'ing into an unfamiliar system and understanding at a glance what it's doing. It is far more annoying to predictably deploy and tightly couples your application configuration to the host system configuration. (Even moreso if you created dedicated users for each application, which I understand is the recommended solution.)
If I'm just holding it wrong and there exists some better tooling to manage podman in prod that I don't know about, I'm happy to hear about it.
Honestly with the hell I'm going through just trying to get Podman to run properly on macos, I can't imagine trusting the Podman people with a production deployment. I was not particularly impressed with the Docker tooling, but Podman has been even worse. This is a not-remotely exhaustive list of things I've run into in the last 24 hours:
* Podman fails to build a 16GB container image (after 30 minutes of downloading dependencies) despite having 90GB free out of a 200GB podman virtual machine
* Podman machine will, for reasons I don't understand, create a filesystem in a block device with wildly different sizes, and it seems like it's just random
* Pushing podman images to a container image registry via the Podman Desktop UI gives no indication that it's doing anything or even recognized the "push image" click, a success or error notification _might_ appear several or tens of minutes later or possibly not at all
* Starting a podman machine might work, but it fails ~75% of the time with not-particularly-exotic options (a bunch of ram and disk) and very cryptic error messages, frequently telling me to file bug tickets (I have)
* Podman Desktop won't let me create a podman machine with more than 44GB of disk, but the podman machine CLI won't let me create a machine with fewer than 100GB (IIRC--it's some number larger than 44, in any case)
Apart from the container image being absurdly large (Python developers love massive packages, I guess), I'm not doing anything exotic.
Yes, I recommend this: https://www.redhat.com/en/blog/kubernetes-workloads-podman-s...
Is there a nice guide for podman that includes quadlets (or saying not to use them?) I find lots of guides stray into things that work on redhat, and on my Linuxes of choice, Raspbian and Ubuntu, things aren't straightforward.
I find the podman man pages quite readable and thorough if you've had experience configuring systemd services. Good examples as well.
https://docs.podman.io/en/latest/markdown/podman-systemd.uni...
Thanks - you're right. I just have never known whether to quadlet or not to quadlet, as the centre of gravity seems to be moving that way, but that might be a redhat-first feature.
Can't comment on Raspbian, but Ubuntu LTS (has/had) a seriously outdated podman version. This is the kind of nuisance the Debian derivatives have been running into for more than 20 years: they are extremely conservative, and if that is all you need, then that is great, but if not, you'll have to either run the latest Ubuntu (not LTS), or you upgrade to something like fedora.
In many cases, Debian unstable is also a good choice.
Is there no upstream package repo like docker has.
> they are extremely conservative, and if that is all you need, then that is great
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.
I like running docker compose for my simple needs because it consolidates pretty much all the config in one declarative file, and docker manages 'everything'. By now I know how to handle the handful of caveats listed in this article. Beyond what's listed there, I'd also give a mention to the way port publishing works (the fact that it ignores firewalls), as that's something that still trips people up if they don't know about it.
> 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.
> 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.
We just use ansible for this part.
> I'd also give a mention to the way port publishing works (the fact that it ignores firewalls), as that's something that still trips people up if they don't know about it.
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 :)
Sounds like they're referring to a dedicated tenancy multi-tenant model
I really want something that is Docker Compose but for Kubernetes. I mean that I can have a simple way to declaring resources in just like Docker Compose, but I run the environment in Kubernetes so that I can get to test the behaviors when there are multiple copies of the softwares running together. I do rely on Kubernetes heavily for distributed and networked software deployment, so it is even better if we can emulate things like latency or burstable packet loss so that we can do a controlled chaos test for reliability test. I tried Skaffold, Tilt, Devspaces and Devpod/Coder v2, none of them are really simple like Compose.
This is a problem we, as a company, have thought about a lot, but we always concluded that Kubernetes is already the simplest abstraction of a distributed system that is feasible for the diverse needs that the biggest companies out there have.
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.
Same, I've tried three or four times to make it work, including one attempt that just translated compose.yaml into k8s yaml, and every time I came away thinking, "just use k8s". K8s yaml looks complex, and can start to feel very boilerplate, but attempts to hide the complexity often just lead to something not-flexible-enough because it encodes convention over configuration, and inevitably some project runs into limitations and pretty soon you've just built an abstraction layer that leaks or is equally complex/verbose and now you have to learn something new.
Just use k8s and follow similar patterns is the conclusion I've arrived at personally.
Helm mostly does that. Not a huge fan of a text templating engine generating yaml but once you get your chart setup with a few variable inputs, you can continue using it for a bunch of other stuff with minimal new config.
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)
I've recently been dipping my toes into k8s / kustomize / helm, and I recently had a situation where having a base deployment yml template that I wanted to reuse across various deployments. I had a look at Helm and I was frankly shocked how bad the templating was with Go templates, it was close to unreadable and felt very brittle!
Yeah, that's fair. I don't think it's as bad if you make your own charts and can more liberally hardcode things. Community charts tend to have an insane amount of "knobs" so you can basically change everything being templated.
I don't know if I'd necessarily call it brittle, though. You can use `helm template` and various linters to validate the generated yaml is correct (and use something like pre-commit to autorun)
How so? I find it much the same to other templating engines like Jinja, though I'm definitely not a fan of the syntax. But that hardly matters anymore with LLMs.
I think it's a combination of text templating + yaml (which is whitespace delimited). When templating html with Jinja, it's not a big deal if the indentation isn't right. In helm, best case you get a syntax error, worst case you end up overwriting keys and producing something syntactically but not logically correct.
I did that too, and ended up just skipping helm and using envsubst to interpolate the values I need at runtime from env vars. Nearly everyone preferred that approach. YMMV of course.
I think that's a good approach if it works for your use case. Sometimes you might want something slightly more sophisticated like basic logic (loops/conditionals). In those cases, you can still use helm but you have an extremely simple template and avoid many of the "can't read this template" helm pitfalls.
BTW, this is what Flux V2 (GitOps) supports as well and we do at our company. It's worked well and covered almost all our use cases.
Compose is great, but a couple things always created friction for me when using it for non-local setups:
* 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.
[1] https://github.com/psviderski/uncloud/
For remote installation, use the `docker context` command. You create a context with a named SSH host and then it connects via SSH to that host (as configured in your local ssh_config) and uses its docker daemon. Everything works flawlessly apart from local bind mounts (for obvious reasons).
If you remember `docker machine`, this is basically the modern version of that.
That's fair; good solution.
> Lack of a user-friendly way of managing a Docker Compose installation on a remote host
I've been using portainer for years, it's decent.
I am using systemd + go binary deploy. Running 10 years+ in production. Meanwhile docker based setup fail every now and then. And kubernetes? well forget about it.
What I'd like is systemd-compose. Maintaining several dozens of .service-files is not my idea of fun.
It's not quite what you suggested, but you can use podman with systemd.
https://docs.podman.io/en/latest/markdown/podman-systemd.uni...
Or more simply just wrap some orchestrator in a single service file. Could just be a bash script used for ExecStart.
i don't have dozens of services. My complex SaaS has total 6 services and each one is running on its own server.
Service file lives in the mono repo where all 6 services live.
Makes it trivial to make changes and redeploy.
Keeping your sanity in tech is underrated.
How does this work? Can you show it?
I mean no disrespect. This is more of a rant at how things are today. It is telling that over-complicated solutions have become so common that, for the current generation of devs, Kubernetes is the obvious way of doing stuff and a simple systemd service is the obscure one. I am sure there are good reasons for this, but it still feels like a loss when simplicity is no longer obvious.
That's like comparing you being able to make a salad from tomatoes and an industrial tomato sauce making facility. Both take tomatoes and end up with food, but the scale is completely different.
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.
If you love docker compose then you would love k3s. A single server with k3s is basically docker compose + the possibility to use helm to install all kinds of open source project such as monitoring and it just works.
I am using docker-compose everywhere. I really enjoy using it. I have a single thing that is annoying for normal production deployments, and that is that it isn't super easy to have a rolling deployment, I just need two replicas for zero downtime deployment, and I don't really want docker swarm. I think it is the networking which breaks at that point, and you have to have a more involved setup, and at that point I'd just use kubernetes, as I know how that works.
Could i survive with 10 seconds of downtime, probably, but I'd really like if I could avoid it.
That's why I now use uncloud, simple as docker compose and got rolling deployments
https://uncloud.run/docs/guides/deployments/rolling-deployme...
I’m happily taking that 10sec whenever thinking about the lifting I have to do for kube and extra cost.
> How Do You Handle Deployments?
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.
Record the existing container id, rescale the service to 2 instances (hence bringing a second container up), wait for the second one to be healthy, (optional) stop directing traffic to the old container, wait a few seconds, stop the old container, rescale the service back to 1 instance.
Here's a CLI plugin that automates this: https://github.com/wowu/docker-rollout
Blue Green Deployment. There must be a docker container to handle this or at least a bash script.
edit: thanks to next comment for referencing one
Surprised they didn't mention docker compose secrets - https://docs.docker.com/reference/compose-file/secrets/
To be honest I never really understood the benefit of Docker (Compose) Secrets - which is different from Swarm Secrets. Imho there just plain host mounted volumes, which are hidden from inspect commands?
I prefer Portainer to manage my docker composes. It is simple and can do it all instead of using cli. Added benefit if you have multiple hosts and want to manage them from one place. And you can extend the whole setup with git for version control.
Kubernetes sounds like overkill, but I've been running microk8s for few standalone servers. This feels a pretty good match when working with agents. Codex can manage the cluster also over ssh, schedule new pods, check statuses, logs etc.
K3s is also pretty nice for a minimal setup
Haven't used it in a while but this thing is also interesting--it supports a bunch of different ways to spin up k8s https://github.com/tilt-dev/ctlptl
I think k8s is a great choice today specially when you can plug it into Gitlab and have a control plane for your clusters in the same place where your code lives.
I do this via Dokploy on a hosted Linode VPS and absolutely love it. Super easy to set up and maintain for tons of little side projects that don't require tons of resources.
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.
Very cool article. Wish it didn't have silly AI-isms:
> This is the shape Distr lands on
It's an AI company, it's kind of expected at this point - who would take an AI company seriously if they don't use AI themselves?
Why do you say it's an AI company? It seems like their business is "Distribute your application to self-managed customers" not especially AI focused.
They said they help deployments for "software companies and AI companies" which I thought was an interesting distinction
I think it's just to get the AI keyword in there.
That's just different customer personas for marketing reasons, just like Vercel has "Build and deploy on the AI Cloud" as their main tag line on the landing page. It doesn't mean they are an "AI company".
Every company these days are AI companies. Even the ones you’d least expect. https://www.bbc.com/news/articles/c98mrepzgj7o
Sure, but you wouldn't just say "Oh they are a Postgres" company because they use that specific database somewhere in the stack.
I was being flippant
Only in certain bubbles, which certain people have trouble realising they're in
I quite like the “shape” term; every type of (sigh…) stakeholder … understands it, and I don’t need to swap in terms like “interface”, api, contract, architecture, structure, etc - unless I want to talk specifically about that thing. Everyone can fit a triangle and parallelogram in their mind, which is just dandy when I’m just trying to communicate difference.
“Lands on”? I like that less.
I think it just means "This is how Distr works", which seems clear enough.
> Every docker compose pull keeps the previous image on disk. Every container with the default json-file log driver writes unbounded JSON to /var/lib/docker/containers/<id>/<id>-json.log. On a busy host this is one of the most common reasons for an outage: the disk fills and Docker stops being able to write anything
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
A very simple fix for that is to use the systemd log driver to send all the container logs to journald. Then you can set a size or time limit on journald.
https://docs.docker.com/engine/logging/drivers/journald/
I believe Podman can do something similar.
Sure, it's stable enough, just keep in mind you won't get any autoscaling (or manual for that matter). Swarm is still supported by a third party, but that party has been loudly signaling that they intend to kill it off this year or next. Kubernetes isn't too big a leap, but damn are all those yaml manifests annoying to maintain. I usually just copy and tweak them from another project.
Somewhat adjacent in how I look at using Docker at all in prod, here's what I always wonder:
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.
> if I run my "build" processes (npm, composer, etc) on the server at deploy time
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.
I would say it's bad practice because you end up having to copy all the build dependencies (source code) to the host and you're potentially putting a bunch of extra load on the host during the build process.
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.
Have a look at multi stage container builds. Your images should not need a build step at start, the result should be in the baked image. Else you become reliant on fetching packages during build etc.
One big thing I think is whether you want some sort of non-trivial network configuration, such as multiple external IPs via ipvlan. That's technically possible off docker, but not in a responsible way as anything in the ipvlan will be accessible to the public internet. Overall the implementations for this are very janky and occasionally enter tilted states that are close to impossible to recover from short of a restart of the docker daemon.
Sure why not, it just never fits into my model of how I design infrastructure.
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.
I really like developing against compose because it's light but gives you that escape hatch of translating to k8s if later circumstances call for it.
Very few separate ecosystem transfers are quite that frictionless.
My experience with docker-compose is a bit outdated, but my impression some years ago was that it was too sensitive and fragile. I encountered bugs or incompatibilities that broke the docker-compose setup often enough to be forced to pin the specific docker and docker-compose versions.
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.
That was the old docker compose. Things got a lot better since they rewrote it in Golang and update it again.
TIL about limiting logs. Very useful, I had no idea.
Yes of course, im running a few production projects with it.
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.
Couple of months ago I published the way I use docker compose in production with mushak.sh and it’s really convenient to deploy this way
Yes.
It's nice to get an easy question every once in a while.
Use Nspawn. It's on every machine.
I am doing just this. Running docker compose on a server. When there will be to many microservices, we will move them in managed Kubernetes on a cloud platform or Nomad if any cloud platform offers it.
well written guide.
even their follow up - Docker Compose vs Kubernetes.
Docker compose for me has been great - no complexity.
Yes. It's perfectly fine.
Yes and no :)
hahah came here to say: "It depends."
Personally I have moved to k3s, although after learning a bit too deep how k8s operates when writing custom controllers at the day job.
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....
No
No
I really liked the specific actionable steps in the TLDR.
> if you close the operational gaps it leaves: cleanup, healing, image pinning, socket security, and updates.
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.
AI article with 27 occurrences of dashes —