Solving Docker Compatibility Issues with Kamal and Ruby 2.2.2
Reading time: ~ 5 minutes

The Modernization Paradox
Modern deployment tools like Kamal promise simplicity, speed, and power—but what happens when you’re working with a legacy Ruby app that predates many of those tools?
We recently helped a client deploy a Ruby 2.2.2 application using Kamal—Basecamp’s container-based deployment solution—and what seemed like a routine task turned into a deep dive into Docker history, deprecation policies, and compatibility cliffs.
This post walks through what happened, why it happened, and how we solved it.
The Challenge: Ruby 2.2.2 Meets Modern Containers
Our client’s app runs on Ruby 2.2.2, which reached end-of-life in 2018. While it might sound ancient in tech years, it’s not uncommon—many businesses still rely on stable legacy apps that haven’t been fully modernized. The challenge wasn't just about deploying an old application—it was about doing so in a modern, containerized environment.
Here was our setup:
- Ruby version: 2.2.2
- Deployment tool: Kamal
- Base image:
ruby:2.2.2-slim
- Goal: Containerize and deploy using modern best practices
Everything looked straightforward—until it wasn’t. We hit a roadblock that took us down a long rabbit hole of Docker internals and version compatibility issues.
The Error That Started It All
Our first deployment attempt failed with this error:
ERROR: failed to solve: failed to load cache key: Pulling Schema 1 images have been deprecated and disabled by default since containerd v2.0
At first glance, this error seemed to come out of nowhere. We were using a standard Ruby base image, after all. But as we dug deeper, we discovered that this error represented a fundamental shift in how Docker handles container images.
Understanding Docker Schema Evolution
Docker schemas define the structure and format of configuration files—such as Dockerfiles, Compose files, and image manifests—that specify how Docker containers should be built, configured, and run. The ruby:2.2.2-slim image uses Docker Image Manifest Schema 1, which is essentially the "old way" of doing things.
Here’s the timeline:
Docker v26+: Schema 1 images are blocked by default
Docker v28+: Schema 1 support is completely removed
So when Kamal tried to pull the image, Docker refused, creating a perfect storm for our legacy Ruby application: we needed an old base image that was no longer supported by modern Docker versions.
Kamal's Build Strategy: The Hidden Complexity
Kamal uses the docker-container driver by default for building images. This driver runs the build process inside a separate container using an internal BuildKit engine. While this approach provides isolation and consistency, it also means that the build process doesn't inherit settings from your host Docker daemon.
This became our first learning moment: enabling Schema 1 support in your local Docker daemon has no effect when using Kamal's default builder because it operates in its own isolated environment.
We tried three different approaches before finding the one that worked.
The Solution Journey: Three Attempts to Success
Attempt 1: Enable Schema 1 Support in Docker
We first tried enabling legacy schema support by setting this environment variable:
[Service] Environment="DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1"
Result: No effect. Because Kamal’s build container doesn’t inherit host daemon settings, this fix didn’t apply.
Attempt 2: Switch Kamal’s Build Driver
We found Kamal issue #937, which suggested switching to the docker
build driver. This would make Kamal use the host Docker engine directly.
Update in kamal.yml
:
builder: driver: docker
Result: Kamal now respected the host Docker settings. Progress! But…
Attempt 3: Downgrade Docker Version
We still hit the same error—because our host machine was running Docker v28, which had removed Schema 1 support entirely. Even with the proper build driver and config, the image simply couldn’t be pulled.
Final fix: We downgraded Docker to v26, where Schema 1 could still be enabled.
# After downgrade: sudo systemctl restart docker
That finally resolved the image issue. The legacy Ruby app was able to build and deploy using Kamal.
The SSL Certificate Curveball
Just when we thought we had solved the main deployment challenge, we encountered another issue: SSL certificate management. Kamal ships with built-in support for Let's Encrypt via its built-in proxy, which works great in most cases. But our client had already purchased and configured a custom SSL certificate, and Kamal doesn’t support third-party certs out of the box.
Our workaround:
- Disabled Kamal’s proxy
- Added Nginx as an accessory
- Manually configured the existing cert in the Nginx config
This allowed us to respect the client’s existing SSL infrastructure while keeping the deployment containerized.
Key Takeaways for Legacy Application Deployment
This experience taught us several valuable lessons about deploying legacy applications with modern tools:
Choose the Right Build Driver for Kamal
We learned that Kamal’s default docker-container
driver ignores host Docker settings, which can block important config changes. \
→ If you’re troubleshooting builds, switching to the docker
driver can give you more visibility and control.
Check Docker Version Compatibility Early
We discovered that newer versions of Docker no longer support older image schemas, which can silently break legacy deployments.
→ Always confirm that your Docker version supports the base image your app needs—especially for older Ruby versions.
Be Ready to Customize SSL Setup
Kamal assumes you’ll use Let’s Encrypt, but our client needed a custom certificate—which wasn’t supported out of the box.
→ If you or your clients use paid SSL certs, be prepared to disable Kamal’s proxy and configure a custom solution like Nginx.
Modern Tools Don’t Always Work Backwards
We assumed modern tools would adapt to old systems, but the reality is they often expect modern conventions.
→ Don’t just focus on learning the tool—take time to understand how legacy constraints will interact with it.
Expect Gaps in Documentation (and Fill Them)
We found that combining legacy apps with new tools often led us into undocumented territory.
→ Be ready to dig through GitHub issues and share your findings—your workaround might help the next dev in your shoes.
Looking Forward: Lessons for Future Legacy Deployments
Legacy application deployment isn’t just about keeping old code alive—it’s about understanding how today’s tools interact with yesterday’s standards.
As the dev ecosystem continues to evolve, these kinds of edge cases will continue to pop up. The good news? You don’t always need to rewrite or rebuild everything. With a bit of digging and a willingness to adapt, you can deploy even very old systems in a modern, secure, containerized way.
Whether it's a Ruby 2.2.2 application or any other legacy system, the principles remain the same: understand the toolchain, respect version boundaries, and be prepared to adapt when the obvious solutions don't work.
In the end, our client's application is now successfully deployed using modern containerization while maintaining its legacy Ruby foundation. It's a testament to the fact that with the right approach, even the most challenging legacy deployments can be brought into the modern era.
We Think You’ll Also Enjoy Reading…
- Deploying a Ruby on Rails app to DigitalOcean Using Kamal
- Legacy App Modernization: A Case Study in Rails 8 and Rapid Prototyping
- Troubleshooting Docker Desktop: Tips and Alternatives for Developers
FAQ’s
Q: Why did Kamal fail to pull the ruby:2.2.2-slim image?
Because the image uses Docker’s deprecated Schema 1, which is no longer supported in Docker v28+ and disabled by default in v26+. Kamal couldn’t pull it using its default build driver.
Q: How can I enable Schema 1 image support in Docker?
You can set the following environment variable in your Docker daemon configuration (only works with Docker ≤ v27):
[Service] Environment="DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1"
Then restart the Docker service:
sudo systemctl restart docker
Q: Why didn’t that fix work for me when using Kamal?
Kamal’s default build driver (docker-container
) uses an isolated containerized BuildKit environment, which doesn’t inherit host-level Docker settings.
Q: How do I switch Kamal to use my host Docker daemon instead?
Update your kamal.yml
to include:
builder: driver: docker
This allows Kamal to use your local Docker configuration, including the Schema 1 override.
Q: What if I’m running Docker v28 or higher?
You’ll need to downgrade Docker to v27 or earlier, as Schema 1 support has been completely removed in v28+.
Q: How can I use a custom SSL certificate with Kamal?
Kamal’s built-in proxy only supports Let’s Encrypt. To use a custom or paid SSL certificate:
Disable Kamal’s proxy
Add Nginx as an accessory service
Manually configure your certificate paths inside the Nginx container
Q7: Is it safe to use an old Ruby version like 2.2.2 in production?
It’s not ideal—Ruby 2.2.2 reached end-of-life in 2018 and no longer receives security updates. If you're deploying it, use isolation via containers and plan an upgrade strategy as soon as possible. We can chat with you about your options.