Shipping a Python service to production
Table of Contents
I have a Python service that passed all its tests and I want to ship it. The build needs compilers and dev headers to install native dependencies, but in production I want the smallest possible image — no shell, no package manager, nothing an attacker could use. This is the classic multi-stage build: build in Debian, run in distroless.
First I set up my Dagger project and install daggerlib:
dagger init --sdk=python
dagger install github.com/Konubinix/daggerlib
The following is only for testing purposes — it makes the project use the local daggerlib checkout instead of the published version:
sed -i '/pin/d; s|"github.com/Konubinix/daggerlib@main"|"../.."|; s|"\.\./\.\.",|"../.."|' dagger.json
Here is my service — it logs a startup message with timezone info:
"""Minimal service that logs a startup message with timezone info."""
from datetime import datetime
tz = datetime.now().astimezone().tzname()
print(f"service started (timezone: {tz})")
Then I write my Dagger module. The <<run-distroless>> reference is
defined further below:
from typing import Annotated
import dagger
from dagger import DefaultPath, dag, function, object_type
@object_type
class ProductionService:
@function
async def run_distroless(self, src: Annotated[dagger.Directory, DefaultPath(".")]) -> str:
"""Run a Python service in the distroless image."""
return await (
dag.lib().distroless_python3_debian()
.with_file('/app/service.py', src.file('service.py'))
.with_exec(['python3', '/app/service.py'])
.stdout()
)
1. Running the service in distroless
For my Python service, I use the distroless Python image. It ships only the Python interpreter — no pip, no shell, no debugging tools. I inject my service and run it:
@function
async def run_distroless(self, src: Annotated[dagger.Directory, DefaultPath(".")]) -> str:
"""Run a Python service in the distroless image."""
return await (
dag.lib().distroless_python3_debian()
.with_file('/app/service.py', src.file('service.py'))
.with_exec(['python3', '/app/service.py'])
.stdout()
)
dagger call run-distroless