Building pyodbc for AWS Lambda’s Python 3.13 Runtime

In my previous post, I walked through building pyodbc for Python 3.9 Lambda functions. With the release of Python 3.13 support in Lambda, it’s time for an update.

Here’s the Dockerfile that builds a Lambda container image with pyodbc and SQL Server support:

Dockerfile
FROM public.ecr.aws/lambda/python:3.13 AS builder

ENV ODBCINI=/opt/odbc.ini
ENV ODBCSYSINI=/opt/
ARG UNIXODBC_VERSION=2.3.12

RUN dnf install -y gzip tar openssl-devel gcc gcc-c++ make automake kernel-devel

RUN curl ftp://ftp.unixodbc.org/pub/unixODBC/unixODBC-${UNIXODBC_VERSION}.tar.gz -O \
    && tar xzvf unixODBC-${UNIXODBC_VERSION}.tar.gz \
    && cd unixODBC-${UNIXODBC_VERSION} \
    && ./configure --sysconfdir=/opt --disable-gui --disable-drivers --enable-iconv --with-iconv-char-enc=UTF8 --with-iconv-ucode-enc=UTF16LE --prefix=/opt \
    && make \
    && make install

RUN curl https://packages.microsoft.com/config/rhel/9/prod.repo > /etc/yum.repos.d/mssql-release.repo
RUN dnf install -y e2fsprogs fuse-libs libss
RUN ACCEPT_EULA=Y dnf install -y msodbcsql18

ENV CFLAGS="-I/opt/include"
ENV LDFLAGS="-L/opt/lib"

RUN mkdir /opt/python/ && cd /opt/python/ && pip install pyodbc -t .

FROM public.ecr.aws/lambda/python:3.13

RUN dnf install -y openssl

COPY --from=builder /opt/python /opt/python
COPY --from=builder /opt/microsoft /opt/microsoft
COPY --from=builder /opt/lib /opt/lib

The key differences from the Python 3.9 version include:

  • Updated base image to public.ecr.aws/lambda/python:3.13
  • Switched to dnf from yum for package management
  • Upgraded to ODBC Driver 18 for SQL Server
  • Upgraded unixODBC to the 2.3.12
  • Added explicit OpenSSL installation in the final image

Example Usage

Create a simple Lambda function (app.py) to test the SQL Server connection:

Python
import os
import pyodbc

def handler(event, context=None):
    conn_str = (
        "DRIVER=/opt/microsoft/msodbcsql18/lib64/libmsodbcsql-18.4.so.1.1;"
        f"SERVER={os.getenv('DB_HOST', 'db')};"
        f"DATABASE={os.getenv('DB_NAME', 'master')};"
        f"UID={os.getenv('DB_USER', 'sa')};"
        f"PWD={os.getenv('DB_PASSWORD')};"
        "TrustServerCertificate=yes"
    )
    
    try:
        with pyodbc.connect(conn_str) as conn:
            with conn.cursor() as cursor:
                cursor.execute("SELECT @@VERSION")
                return str(cursor.fetchone()[0])
    except Exception as e:
        return str(e)

To test the build with a real SQL Server instance, create this docker-compose.yml:

YAML
services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    ports:
      - "1433:1433"
    environment:
      SA_PASSWORD: "YourStrong@Passw0rd"
      ACCEPT_EULA: "Y"
    healthcheck:
      test: ["CMD", "/bin/bash", "-c", "timeout 5 bash -c '</dev/tcp/localhost/1433' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 50
      start_period: 60s

  lambda:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - "9000:8080"
    environment:
      DB_HOST: "db"
      DB_USER: "sa"
      DB_PASSWORD: "YourStrong@Passw0rd"
      DB_NAME: "master"
    volumes:
      - ./app.py:/var/task/app.py
    command: app.handler
    depends_on:
      db:
        condition: service_healthy

  test:
    image: curlimages/curl:latest
    depends_on:
      - lambda
    command: >
      sh -c "sleep 5 && curl -X POST 'http://lambda:8080/2015-03-31/functions/function/invocations' -d '{}'"

Run it:

Bash
docker compose up --build

The Lambda function will connect to SQL Server and return its version string, verifying that the pyodbc connection is working correctly:

Bash
lambda-1  | 11 Jan 2025 20:32:54,776 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
test-1    |   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
test-1    |                                  Dload  Upload   Total   Spent    Left  Speed
lambda-1  | START RequestId: 710a4f6b-cef8-471c-9fb2-1a96eb3d355c Version: $LATEST
lambda-1  | 11 Jan 2025 20:33:00,058 [INFO] (rapid) INIT START(type: on-demand, phase: init)
lambda-1  | 11 Jan 2025 20:33:00,058 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.
lambda-1  | 11 Jan 2025 20:33:00,058 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false
lambda-1  | 11 Jan 2025 20:33:00,195 [INFO] (rapid) INIT RTDONE(status: success)
lambda-1  | 11 Jan 2025 20:33:00,195 [INFO] (rapid) INIT REPORT(durationMs: 136.556000)
lambda-1  | 11 Jan 2025 20:33:00,195 [INFO] (rapid) INVOKE START(requestId: 45044b00-449a-47ee-b26d-f6f30fe9cc4a)
lambda-1  | 11 Jan 2025 20:33:00,267 [INFO] (rapid) INVOKE RTDONE(status: success, produced bytes: 0, duration: 72.559000ms)
lambda-1  | END RequestId: 45044b00-449a-47ee-b26d-f6f30fe9cc4a
lambda-1  | REPORT RequestId: 45044b00-449a-47ee-b26d-f6f30fe9cc4a	Init Duration: 0.06 ms	Duration: 209.34 ms	Billed Duration: 210 ms	Memory Size: 3008 MB	Max Memory Used: 3008 MB	
100   208  100   206  100     2    939      9 --:--:-- --:--:-- --:--:--   949
test-1    | "Microsoft SQL Server 2022 (RTM-CU16) (KB5048033) - 16.0.4165.4 (X64) \n\tNov  6 2024 19:24:49 \n\tCopyright (C) 2022 Microsoft Corporation\n\tDeveloper Edition (64-bit) on Linux (Ubuntu 22.04.5 LTS) <X64>"
test-1 exited with code 0
Share this: Facebooktwitterlinkedin