Skip to content

Migrating from Docker

If you’re coming from Docker, this guide helps you translate concepts and migrate your workloads.

DockertenementNotes
DockerfileYour app codeNo container images needed
docker-compose.ymltenement.tomlSimilar structure, simpler
ContainerInstanceRunning process
ImageCommandJust the executable
Volumedata_dirPersistent storage
Network(automatic)tenement handles routing
Port mappingPORT env varAuto-allocated
Health checkhealth = “/health”Same concept

From Docker:

FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

To tenement:

./compiled-binary
# Just run directly with your package manager
uv run python app.py
# or: bun run server.ts

Key point: No container image. Your app runs directly on the host with its dependencies.

From docker-compose:

version: "3"
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://...
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
deploy:
resources:
limits:
memory: 256M

To tenement.toml:

[service.api]
command = "uv run python app.py"
health = "/health"
memory_limit_mb = 256
[service.api.env]
DATABASE_URL = "postgres://..."

Before (Docker): Hardcoded port

app.run(host="0.0.0.0", port=3000)

After (tenement): Read from PORT

import os
port = int(os.getenv("PORT", "3000"))
app.run(host="127.0.0.1", port=port)

Key changes:

  1. Read PORT from environment (tenement sets it)
  2. Bind to 127.0.0.1 not 0.0.0.0 (tenement handles external access)
Docker Featuretenement Alternative
Container imagesPre-install deps, use uv/bun/nix
Image registryDeploy binaries directly
Layer cachingNot needed (no build step)
Compose networksLocalhost + routing
docker execten health, API logs
Multi-arch imagesBuild for target arch
BenefitWhy
Faster startupNo container overhead
Less memory~20MB vs ~50-100MB per container
Simpler debuggingNo container abstraction
Native performanceSyscalls go directly to kernel
Smaller footprintNo Docker daemon
Scale-to-zeroBuilt-in idle timeout
docker-compose.yml
services:
api:
build: ./api
ports: ["3000:3000"]
worker:
build: ./worker
tenement.toml
[service.api]
command = "uv run python api/app.py"
health = "/health"
[service.worker]
command = "./worker/worker"
health = "/health"
[instances]
api = ["prod"]
worker = ["bg-1", "bg-2"]
docker-compose.yml
services:
api:
env_file: .env
tenement.toml
[service.api.env]
DATABASE_URL = "${DATABASE_URL}" # From host environment
SECRET_KEY = "${SECRET_KEY}"

Load with: source .env && ten serve

docker-compose.yml
services:
api:
volumes:
- ./data:/app/data
tenement.toml
[settings]
data_dir = "./data"
[service.api]
storage_persist = true
[service.api.env]
DATA_PATH = "{data_dir}/{id}"
Terminal window
docker build -t myapp .
docker push registry.example.com/myapp:v2
docker-compose up -d
Terminal window
# Build locally (if compiled)
cargo build --release
# or just deploy code directly
# Deploy
rsync -avz ./app server:/opt/myapp/
ssh server "ten deploy api --version v2"

Q: Do I need to rewrite my Dockerfile? A: No Dockerfile needed. Just ensure your app reads PORT from environment.

Q: What about my CI/CD pipeline? A: Skip the image build step. Deploy code directly via rsync, git pull, or your preferred method.

Q: Can I use container images with tenement? A: No. tenement runs processes, not containers. Extract your app from the container.

Q: What about Docker secrets? A: Use environment variables. For production, inject secrets at runtime (Vault, Doppler, etc.).