LakeQL
Introduction
  • Overview
  • Key Concepts
  • Package Map
Getting Started
  • Prerequisites
  • Quickstart
  • Environment Configuration
  • First Run
Architecture
  • System Overview
  • Data Flow
  • Request Lifecycle
Configuration
  • Environment Variables
  • Authentication
  • Trino Connection
create-app
  • Usage
  • Template Structure
  • Post Creation
Contributing
  • Local Development
  • Contribution Guide
Guides
  • Custom Resolvers
  • Extending Schema
  • Deploying
  • Mutations
  • Load Strategies
GitHub
LakeQL
  1. LakeQL
  2. Guides
  3. Deploying

On this page

  1. Production Build
  2. Docker
  3. Docker Compose
  4. Environment Variables
  5. Health Check
  6. Production Checklist
  7. Scaling
  8. Platform-Specific Notes
    1. AWS ECS / Fargate
    2. Google Cloud Run
    3. Kubernetes

Deploying

Build and deploy a LakeQL API to production with Docker, Compose, and environment variable management.

Production Build #

LakeQL projects use tsdown for building TypeScript into production-ready JavaScript:

1
2
pnpm run build

This produces optimized output in the dist/ directory. The entry point is dist/index.mjs.

1
2
3
# Run the production build locally
node dist/index.mjs

Docker #

The app template includes a multi-stage Dockerfile that produces a minimal production image:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55# Build stage
FROM node:24-alpine AS builder

WORKDIR /app

ARG PNPM_VERSION=11
RUN npm i -g pnpm@${PNPM_VERSION} && \
    pnpm config set store-dir ~/.pnpm-store

# Install dependencies
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Copy source and build
COPY . .
RUN pnpm run build

# Production stage
FROM node:24-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production

# OCI image labels for traceability
ARG COMMIT_SHA
ARG SOURCE_URL
ARG VERSION
ARG LICENSE
ARG AUTHORS

LABEL org.opencontainers.image.revision=${COMMIT_SHA} \
      org.opencontainers.image.source=${SOURCE_URL} \
      org.opencontainers.image.version=${VERSION} \
      org.opencontainers.image.licenses=${LICENSE} \
      org.opencontainers.image.authors=${AUTHORS}

# Copy built output and production dependencies only
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
COPY --from=builder /app/node_modules ./node_modules

# OpenShift compatibility: allow random UID (root group) to access files
RUN chgrp -R 0 /app && \
    chmod -R g=u /app

USER 1001

EXPOSE 4000

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:4000/live || exit 1

CMD ["node", "dist/index.mjs"]

Build and run:

1
2
3
4
5
6
7
docker build -t my-lakeql-api \
  --build-arg COMMIT_SHA=$(git rev-parse HEAD) \
  --build-arg SOURCE_URL=$(git remote get-url origin) \
  --build-arg VERSION=1.0.0 \
  .
docker run -p 4000:4000 --env-file .env.production my-lakeql-api

Docker Compose #

The template includes a compose.yaml for running the API alongside MinIO (S3-compatible storage for the mutation write pipeline):

1
2
3
4
5
6
7
8
9
# Start all services
docker compose up -d

# View logs
docker compose logs -f app

# Stop
docker compose down

The Compose setup includes:

  • app — The LakeQL API server on port 4000
  • minio — S3-compatible storage on port 9000 (console on 9001)
  • minio-init — Creates the default bucket on startup
The Compose file does not include Trino or Hive Metastore. These are infrastructure services that should be managed separately. Configure the HIVE_HOST environment variable to point to your Trino cluster.

Environment Variables #

For production, set environment variables through your deployment platform rather than .env files:

1
2
3
4
5
6
7
8
9
10
11
12
docker run -p 4000:4000 \
  -e HIVE_HOST="https://trino.prod.internal" \
  -e HIVE_PORT=8446 \
  -e HIVE_USERNAME="lakeql-service" \
  -e HIVE_PASSWORD="$TRINO_PASSWORD" \
  -e HIVE_CATALOG=hive \
  -e API_PORT=4000 \
  -e API_LOGGER=warn \
  -e AUTH_MOCK=false \
  -e NODE_ENV=production \
  my-lakeql-api
VariableRequiredDescription
HIVE_HOSTYesTrino server URL
HIVE_PORTYesTrino port (default: 8080)
HIVE_CATALOGYesTrino catalog name
HIVE_USERNAMEYesTrino username
HIVE_PASSWORDNoTrino password
HIVE_SOURCENoSource identifier (default: "lakeql")
AUTH_MOCKNoEnable mock authentication (default: false)
AUTH_MOCK_TOKENNoToken for mock auth
API_PORTNoServer port (default: 4000)
API_LOGGERNoLog level: error, warn, info (default: warn)
Never include .env files with real credentials in Docker images. Use secrets management (AWS Secrets Manager, Vault, Kubernetes secrets) for sensitive values.

Health Check #

LakeQL exposes a configurable health check endpoint (default: /live):

1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from "@lakeql/api/config"
import { allConfigs } from "./config-registry"

const baseDir = import.meta.dirname

export const config = defineConfig({
  allConfigs,
  baseDir,
  healthCheckEndpoint: "/live",
  // ...
})

Use this for container orchestrator probes (Docker, Kubernetes, ECS):

1
2
3
4
5
6
7
8
# Kubernetes liveness probe
livenessProbe:
  httpGet:
    path: /live
    port: 4000
  initialDelaySeconds: 5
  periodSeconds: 10

Production Checklist #

Scaling #

  • Stateless — LakeQL API servers are stateless. Scale horizontally behind a load balancer.
  • Connection pooling — Trino handles connections efficiently. Multiple API instances can share the same Trino cluster.
  • Caching — Consider adding HTTP caching (e.g. CDN or Redis) for frequently-accessed, slowly-changing data.
  • Rate limiting — Add rate limiting middleware to protect Trino from excessive query load.

Platform-Specific Notes #

AWS ECS / Fargate #

1
2
3
4
5
6
# Build and push to ECR
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <account>.dkr.ecr.eu-central-1.amazonaws.com
docker build -t lakeql-api .
docker tag lakeql-api:latest <account>.dkr.ecr.eu-central-1.amazonaws.com/lakeql-api:latest
docker push <account>.dkr.ecr.eu-central-1.amazonaws.com/lakeql-api:latest

Google Cloud Run #

1
2
3
4
5
6
gcloud run deploy lakeql-api \
  --source . \
  --port 4000 \
  --set-env-vars "HIVE_HOST=..." \
  --region europe-west1

Kubernetes #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39apiVersion: apps/v1
kind: Deployment
metadata:
  name: lakeql-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: lakeql-api
  template:
    metadata:
      labels:
        app: lakeql-api
    spec:
      containers:
        - name: lakeql-api
          image: my-registry/lakeql-api:latest
          ports:
            - containerPort: 4000
          env:
            - name: HIVE_HOST
              valueFrom:
                secretKeyRef:
                  name: trino-credentials
                  key: host
          livenessProbe:
            httpGet:
              path: /live
              port: 4000
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

Previous page

Extending Schema

Next page

Mutations

Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
src/config.ts
k8s/deployment.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39