
Bun Deploy
- 46 installs
- 4 repo stars
- Updated January 26, 2026
- daleseo/bun-skills
Generates optimized Docker images for Bun apps and CI/CD and Kubernetes deployment configs, minimizing image size versus Node.js.
About
Produces optimized Dockerfiles, Kubernetes manifests, and CI/CD pipelines for Bun applications with multi-platform ARM64/AMD64 builds. Developers use it when containerizing and deploying Bun apps.
- 12+ optimized Dockerfile templates, binary or Alpine
- K8s manifests, HPA, ingress, and GitHub Actions/GitLab CI
Bun Deploy by the numbers
- 46 all-time installs (skills.sh)
- Ranked #760 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/daleseo/bun-skills --skill bun-deployAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 46 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 26, 2026 |
| Repository | daleseo/bun-skills ↗ |
What it does
Generates optimized Docker images for Bun apps and CI/CD and Kubernetes deployment configs, minimizing image size versus Node.js.
Files
Bun Docker Deployment
Create optimized Docker images for Bun applications. Bun's small runtime and binary compilation reduce image sizes by 88MB+ compared to Node.js.
Quick Reference
For detailed patterns, see:
- Dockerfile Templates: dockerfile-templates.md - 12+ optimized templates
- Kubernetes: kubernetes.md - K8s manifests, HPA, ingress
- CI/CD: ci-cd.md - GitHub Actions, GitLab CI, build scripts
- Multi-Platform: multi-platform.md - ARM64/AMD64 builds
Core Workflow
1. Check Prerequisites
# Verify Docker is installed
docker --version
# Verify Bun is installed locally
bun --version
# Check if project is ready for deployment
ls -la package.json bun.lockb2. Determine Deployment Strategy
Ask the user about their needs:
- Application Type: Web server, API, worker, or CLI
- Image Size Priority: Minimal size (40MB binary) vs. debugging tools (90MB Alpine)
- Platform: Single platform or multi-platform (AMD64 + ARM64)
- Orchestration: Docker Compose, Kubernetes, or standalone containers
3. Create Production Dockerfile
Choose the appropriate template based on needs:
Standard Multi-Stage (Recommended)
# syntax=docker/dockerfile:1
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --from=builder --chown=bunuser:bunuser /app/dist ./dist
COPY --from=builder --chown=bunuser:bunuser /app/package.json ./
USER bunuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD bun run healthcheck.ts || exit 1
CMD ["bun", "run", "dist/index.js"]Minimal Binary (40MB)
For smallest possible images:
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build ./src/index.ts --compile --outfile server
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/server /server
EXPOSE 3000
ENTRYPOINT ["/server"]For other scenarios (monorepo, database apps, CLI tools, etc.), see dockerfile-templates.md.
4. Create .dockerignore
node_modules
bun.lockb
dist
*.log
.git
.env
.env.local
tests/
*.test.ts
coverage/
.vscode/
.DS_Store
Dockerfile
docker-compose.yml5. Create Health Check Script
Create healthcheck.ts:
#!/usr/bin/env bun
const port = process.env.PORT || 3000;
const healthEndpoint = process.env.HEALTH_ENDPOINT || '/health';
try {
const response = await fetch(`http://localhost:${port}${healthEndpoint}`, {
method: 'GET',
timeout: 2000,
});
if (response.ok) {
process.exit(0);
} else {
console.error(`Health check failed: ${response.status}`);
process.exit(1);
}
} catch (error) {
console.error('Health check error:', error);
process.exit(1);
}Add health endpoint to your server:
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: Date.now(),
uptime: process.uptime(),
});
});6. Build and Test Image
# Build image
docker build -t myapp:latest .
# Check image size
docker images myapp:latest
# Run container
docker run -p 3000:3000 myapp:latest
# Test health endpoint
curl http://localhost:3000/health7. Setup for Environment
For Local Development with Docker Compose:
Create docker-compose.yml:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"Run with: docker-compose up
For Kubernetes Deployment:
See kubernetes.md for complete manifests including:
- Deployment configuration
- Service and Ingress
- Secrets and ConfigMaps
- Horizontal Pod Autoscaling
- Resource limits optimized for Bun
For CI/CD:
See ci-cd.md for:
- GitHub Actions workflow
- GitLab CI configuration
- Build and push scripts
- Automated deployments
For Multi-Platform (ARM64 + AMD64):
See multi-platform.md for:
- Multi-platform Dockerfile
- Buildx configuration
- Testing on different architectures
8. Update package.json
Add Docker scripts:
{
"scripts": {
"docker:build": "docker build -t myapp:latest .",
"docker:run": "docker run -p 3000:3000 myapp:latest",
"docker:dev": "docker-compose up",
"docker:clean": "docker system prune -af"
}
}Image Size Comparison
Bun produces significantly smaller images:
| Configuration | Size | Use Case |
|---|---|---|
| Bun Binary (distroless) | ~40 MB | Production (minimal) |
| Bun Alpine | ~90 MB | Production (standard) |
| Node.js Alpine | ~180 MB | Baseline comparison |
88MB+ savings with Bun!
Security Best Practices
1. Use non-root user (included in Dockerfiles above) 2. Scan for vulnerabilities: docker scan myapp:latest 3. Use official base images: oven/bun is official 4. Keep images updated: Rebuild regularly with latest Bun 5. Never hardcode secrets: Use environment variables or secret managers
Optimization Tips
Layer caching:
# Copy dependencies first (changes less often)
COPY package.json bun.lockb ./
RUN bun install
# Copy source code last (changes more often)
COPY . .
RUN bun run buildReduce layer count:
# Combine RUN commands
RUN bun install && \
bun run build && \
rm -rf tests/Minimize final image:
# Only copy what's needed in runtime
COPY --from=builder /app/dist ./dist
# Don't copy: src/, tests/, .git/, node_modules (if using binary)Completion Checklist
- ✅ Dockerfile created (multi-stage or binary)
- ✅ .dockerignore configured
- ✅ Health check implemented
- ✅ Non-root user configured
- ✅ Image built and tested locally
- ✅ Image size verified (<100MB for Alpine, <50MB for binary)
- ✅ Environment configuration ready (docker-compose or K8s)
- ✅ CI/CD pipeline configured (if needed)
Next Steps
After basic deployment:
1. Monitoring: Add Prometheus metrics endpoint 2. Logging: Configure structured logging 3. Secrets: Set up proper secret management 4. Scaling: Configure horizontal pod autoscaling (K8s) 5. CI/CD: Automate builds and deployments 6. Multi-region: Deploy to multiple regions for redundancy
For detailed implementations, see the reference files linked above.
CI/CD Pipelines for Bun Applications
GitHub Actions
Complete workflow for building and deploying Bun Docker images.
Basic Build and Push
Create .github/workflows/deploy.yml:
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run tests
run: bun test
- name: Build application
run: bun run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
# Add your deployment commands here
# e.g., kubectl apply, helm upgrade, etc.
echo "Deploying to production..."GitLab CI
Create .gitlab-ci.yml:
stages:
- test
- build
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
test:
stage: test
image: oven/bun:latest
script:
- bun install --frozen-lockfile
- bun test
only:
- merge_requests
- main
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/bun-app app=$DOCKER_IMAGE
only:
- main
environment:
name: productionDocker Build Scripts
Build Script
Create scripts/docker-build.sh:
#!/usr/bin/env bash
set -e
# Variables
IMAGE_NAME="${IMAGE_NAME:-myapp}"
VERSION="${VERSION:-latest}"
REGISTRY="${REGISTRY:-}"
# Build image
echo "🔨 Building Docker image..."
docker build -t ${IMAGE_NAME}:${VERSION} .
# Tag for registry if specified
if [ -n "$REGISTRY" ]; then
docker tag ${IMAGE_NAME}:${VERSION} ${REGISTRY}/${IMAGE_NAME}:${VERSION}
echo "✅ Tagged as ${REGISTRY}/${IMAGE_NAME}:${VERSION}"
fi
# Show image size
echo ""
echo "📦 Image size:"
docker images ${IMAGE_NAME}:${VERSION} --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
echo ""
echo "✅ Build complete!"Push Script
Create scripts/docker-push.sh:
#!/usr/bin/env bash
set -e
IMAGE_NAME="${IMAGE_NAME:-myapp}"
VERSION="${VERSION:-latest}"
REGISTRY="${REGISTRY:-docker.io}"
# Build if not exists
if ! docker images ${IMAGE_NAME}:${VERSION} -q | grep -q .; then
echo "Image not found, building..."
./scripts/docker-build.sh
fi
# Login to registry
echo "🔐 Logging in to ${REGISTRY}..."
docker login ${REGISTRY}
# Push image
echo "⬆️ Pushing ${REGISTRY}/${IMAGE_NAME}:${VERSION}..."
docker push ${REGISTRY}/${IMAGE_NAME}:${VERSION}
echo "✅ Push complete!"Multi-Platform Build Script
Create scripts/docker-build-multiplatform.sh:
#!/usr/bin/env bash
set -e
IMAGE_NAME="${IMAGE_NAME:-myapp}"
VERSION="${VERSION:-latest}"
REGISTRY="${REGISTRY:-}"
# Create buildx builder if not exists
if ! docker buildx ls | grep -q multiplatform-builder; then
echo "Creating buildx builder..."
docker buildx create --name multiplatform-builder --use
fi
# Build for multiple platforms
echo "🔨 Building multi-platform image..."
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ${IMAGE_NAME}:${VERSION} \
${REGISTRY:+--tag ${REGISTRY}/${IMAGE_NAME}:${VERSION}} \
${PUSH:+--push} \
.
echo "✅ Multi-platform build complete!"Make scripts executable:
chmod +x scripts/docker-*.shCircleCI
Create .circleci/config.yml:
version: 2.1
orbs:
docker: circleci/docker@2.0
jobs:
test:
docker:
- image: oven/bun:latest
steps:
- checkout
- run:
name: Install dependencies
command: bun install --frozen-lockfile
- run:
name: Run tests
command: bun test
build-and-push:
docker:
- image: cimg/base:stable
steps:
- checkout
- setup_remote_docker
- run:
name: Build Docker image
command: docker build -t myapp:$CIRCLE_SHA1 .
- run:
name: Push to registry
command: |
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
docker push myapp:$CIRCLE_SHA1
workflows:
build-deploy:
jobs:
- test
- build-and-push:
requires:
- test
filters:
branches:
only: mainAutomated Deployment with ArgoCD
Create argocd-application.yaml:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: bun-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/yourrepo
targetRevision: HEAD
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: truePackage.json Scripts
Add to package.json:
{
"scripts": {
"docker:build": "docker build -t myapp:latest .",
"docker:build:dev": "docker build -f Dockerfile.dev -t myapp:dev .",
"docker:run": "docker run -p 3000:3000 myapp:latest",
"docker:run:dev": "docker-compose up",
"docker:push": "./scripts/docker-push.sh",
"docker:clean": "docker system prune -af",
"ci:test": "bun test --coverage",
"ci:build": "bun run build && docker build -t myapp:ci ."
}
}Environment-Specific Builds
Use Docker build args for environment-specific builds:
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
ARG API_URL
ENV API_URL=${API_URL}Build with:
docker build \
--build-arg NODE_ENV=staging \
--build-arg API_URL=https://staging-api.example.com \
-t myapp:staging .Security Scanning in CI
Add vulnerability scanning:
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.DOCKER_IMAGE }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'Dockerfile Templates for Bun
This reference provides optimized Dockerfile templates for various Bun application types.
Web Application (Standard)
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --from=builder --chown=bunuser:bunuser /app/dist ./dist
COPY --from=builder --chown=bunuser:bunuser /app/package.json ./
USER bunuser
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Compiled Binary (Minimal)
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build src/index.ts --compile --outfile server
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/server /server
EXPOSE 3000
ENTRYPOINT ["/server"]API Server with Database
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
RUN bunx prisma generate
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --from=builder --chown=bunuser:bunuser /app/dist ./dist
COPY --from=builder --chown=bunuser:bunuser /app/prisma ./prisma
COPY --from=builder --chown=bunuser:bunuser /app/package.json ./
USER bunuser
EXPOSE 3000
CMD ["sh", "-c", "bunx prisma migrate deploy && bun run dist/index.js"]Monorepo Application
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
COPY packages/shared/package.json ./packages/shared/
COPY apps/api/package.json ./apps/api/
RUN bun install --frozen-lockfile
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages ./packages
COPY --from=deps /app/apps ./apps
COPY . .
RUN bun run build --filter=api
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --from=builder --chown=bunuser:bunuser /app/apps/api/dist ./dist
COPY --from=builder --chown=bunuser:bunuser /app/apps/api/package.json ./
USER bunuser
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Development Image
FROM oven/bun:1-alpine
WORKDIR /app
# Install development dependencies
RUN apk add --no-cache git
COPY package.json bun.lockb ./
RUN bun install
COPY . .
EXPOSE 3000 9229
CMD ["bun", "run", "--hot", "--inspect=0.0.0.0:9229", "src/index.ts"]Full-Stack Application (Frontend + Backend)
# Frontend build
FROM oven/bun:1-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package.json frontend/bun.lockb ./
RUN bun install --frozen-lockfile
COPY frontend/ ./
RUN bun run build
# Backend build
FROM oven/bun:1-alpine AS backend-builder
WORKDIR /app/backend
COPY backend/package.json backend/bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY backend/ ./
# Runtime
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
# Copy backend
COPY --from=backend-builder --chown=bunuser:bunuser /app/backend ./
# Copy frontend build to public directory
COPY --from=frontend-builder --chown=bunuser:bunuser /app/frontend/dist ./public
USER bunuser
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]Worker/Background Job
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --chown=bunuser:bunuser . .
USER bunuser
# No EXPOSE needed for workers
CMD ["bun", "run", "src/worker.ts"]CLI Tool
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build src/cli.ts --compile --outfile mycli
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/mycli /usr/local/bin/mycli
ENTRYPOINT ["/usr/local/bin/mycli"]Serverless/Lambda Function
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build src/handler.ts --target=node --outdir=dist
FROM public.ecr.aws/lambda/nodejs:20
COPY --from=builder /app/dist ${LAMBDA_TASK_ROOT}/
COPY --from=builder /app/node_modules ${LAMBDA_TASK_ROOT}/node_modules
# Install Bun in Lambda
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
CMD ["dist/handler.handler"]Multi-Platform (ARM64 + AMD64)
FROM --platform=$BUILDPLATFORM oven/bun:1-alpine AS deps
ARG TARGETPLATFORM
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM --platform=$BUILDPLATFORM oven/bun:1-alpine AS builder
ARG TARGETPLATFORM
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --from=builder --chown=bunuser:bunuser /app/dist ./dist
COPY --from=builder --chown=bunuser:bunuser /app/package.json ./
USER bunuser
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Build command:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .With NGINX Reverse Proxy
# App build
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# NGINX + Bun runtime
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
# Install NGINX
RUN apk add --no-cache nginx
# Copy app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
# Copy NGINX config
COPY nginx.conf /etc/nginx/nginx.conf
# Create startup script
RUN echo '#!/bin/sh' > /start.sh && \
echo 'nginx' >> /start.sh && \
echo 'bun run dist/index.js' >> /start.sh && \
chmod +x /start.sh
EXPOSE 80 3000
CMD ["/start.sh"]With Health Checks
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
COPY --from=deps --chown=bunuser:bunuser /app/node_modules ./node_modules
COPY --chown=bunuser:bunuser . .
USER bunuser
EXPOSE 3000
# HTTP health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD bun run -e 'fetch("http://localhost:3000/health").then(r => r.ok ? process.exit(0) : process.exit(1))'
CMD ["bun", "run", "src/index.ts"]Caching Optimization
FROM oven/bun:1-alpine AS deps
WORKDIR /app
# Cache mount for bun install
RUN --mount=type=cache,target=/root/.bun/install/cache \
--mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=bun.lockb,target=bun.lockb \
bun install --frozen-lockfile --production
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]Image Size Comparison
| Configuration | Size | Use Case |
|---|---|---|
| Bun Alpine (full) | ~90 MB | Development, debugging |
| Bun Alpine (multi-stage) | ~50 MB | Production apps |
| Compiled binary (distroless) | ~40 MB | Minimal production |
| Node.js Alpine | ~180 MB | Baseline comparison |
Best Practices
1. Use multi-stage builds to reduce final image size 2. Copy only necessary files to runtime image 3. Use .dockerignore to exclude dev files 4. Run as non-root user for security 5. Enable health checks for container orchestration 6. Use specific version tags instead of latest 7. Leverage build cache with proper layer ordering 8. Scan images for vulnerabilities regularly
Resources
Kubernetes Deployment for Bun Applications
Complete guide for deploying Bun applications to Kubernetes.
Basic Deployment
Create k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: bun-app
labels:
app: bun-app
spec:
replicas: 3
selector:
matchLabels:
app: bun-app
template:
metadata:
labels:
app: bun-app
spec:
containers:
- name: app
image: myregistry/myapp:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
envFrom:
- secretRef:
name: app-secrets
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: bun-app
spec:
selector:
app: bun-app
ports:
- port: 80
targetPort: 3000
protocol: TCP
name: http
type: LoadBalancerSecrets Management
Create k8s/secrets.yaml:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
DATABASE_URL: postgresql://user:pass@db:5432/prod
SESSION_SECRET: your-secret-key
API_KEY: your-api-keyNever commit secrets to git! Use sealed secrets or external secret managers.
ConfigMaps
Create k8s/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
PORT: "3000"Reference in deployment:
envFrom:
- configMapRef:
name: app-configHorizontal Pod Autoscaling
Create k8s/hpa.yaml:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: bun-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: bun-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80Ingress Configuration
Create k8s/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bun-app-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: bun-app
port:
number: 80Deployment Commands
# Apply all manifests
kubectl apply -f k8s/
# Check deployment status
kubectl get deployments
kubectl get pods
kubectl get services
# View logs
kubectl logs -f deployment/bun-app
# Scale manually
kubectl scale deployment bun-app --replicas=5
# Rolling update
kubectl set image deployment/bun-app app=myregistry/myapp:v2
# Rollback
kubectl rollout undo deployment/bun-app
# Check rollout status
kubectl rollout status deployment/bun-appResource Optimization for Bun
Bun applications typically use less memory than Node.js:
resources:
requests:
memory: "64Mi" # Bun uses ~50% less memory
cpu: "50m" # Lower CPU for startup
limits:
memory: "256Mi" # Still safer than Node.js 512Mi
cpu: "300m"Health Checks Best Practices
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30 # Give Bun time to start
periodSeconds: 10 # Check every 10s
timeoutSeconds: 3 # Fail after 3s
failureThreshold: 3 # Restart after 3 failures
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5 # Bun starts fast
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2Multi-Environment Setup
Use Kustomize for environment-specific configs:
k8s/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
├── overlays/
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── production/
│ ├── kustomization.yaml
│ └── patch-replicas.yamlDeploy:
kubectl apply -k k8s/overlays/staging
kubectl apply -k k8s/overlays/productionMonitoring and Observability
apiVersion: v1
kind: Service
metadata:
name: bun-app-metrics
labels:
app: bun-app
spec:
selector:
app: bun-app
ports:
- name: metrics
port: 9090
targetPort: 9090Add Prometheus annotations:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"Multi-Platform Docker Builds for Bun
Guide for building Docker images that work on both AMD64 and ARM64 architectures.
Why Multi-Platform?
- ARM64: Apple Silicon (M1/M2), AWS Graviton, Raspberry Pi
- AMD64: Traditional Intel/AMD servers, most cloud instances
- Bun: Works natively on both architectures
Basic Multi-Platform Dockerfile
FROM --platform=$BUILDPLATFORM oven/bun:1-alpine AS deps
ARG TARGETPLATFORM
ARG BUILDPLATFORM
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM --platform=$BUILDPLATFORM oven/bun:1-alpine AS builder
ARG TARGETPLATFORM
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Build Commands
Setup Buildx
# Create a new builder
docker buildx create --name multiplatform-builder --use
# Verify builder
docker buildx inspect --bootstrapBuild for Multiple Platforms
# Build and push (requires registry)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry/myapp:latest \
--push \
.
# Build and load locally (single platform only)
docker buildx build \
--platform linux/amd64 \
--tag myapp:latest \
--load \
.Build Script
Create scripts/build-multiplatform.sh:
#!/usr/bin/env bash
set -e
IMAGE_NAME="${IMAGE_NAME:-myapp}"
VERSION="${VERSION:-latest}"
REGISTRY="${REGISTRY:-docker.io}"
PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}"
# Ensure builder exists
if ! docker buildx ls | grep -q multiplatform; then
echo "Creating buildx builder..."
docker buildx create --name multiplatform --use
fi
echo "Building for platforms: $PLATFORMS"
# Build and push
docker buildx build \
--platform $PLATFORMS \
--tag ${REGISTRY}/${IMAGE_NAME}:${VERSION} \
--push \
.
echo "✅ Multi-platform build complete!"
echo " Pushed to: ${REGISTRY}/${IMAGE_NAME}:${VERSION}"Platform-Specific Optimizations
Conditional Dependencies
FROM oven/bun:1-alpine AS deps
ARG TARGETARCH
WORKDIR /app
COPY package.json bun.lockb ./
# Install architecture-specific packages
RUN if [ "$TARGETARCH" = "arm64" ]; then \
apk add --no-cache libffi-dev; \
fi
RUN bun install --frozen-lockfile --productionBinary Selection
ARG TARGETARCH
# Copy architecture-specific binaries
COPY bin/app-${TARGETARCH} /usr/local/bin/appTesting Multi-Platform Images
Local Testing with QEMU
# Install QEMU for cross-platform emulation
docker run --privileged --rm tonistiigi/binfmt --install all
# Test ARM64 image on AMD64 machine
docker run --platform linux/arm64 myapp:latest
# Test AMD64 image on ARM64 machine
docker run --platform linux/amd64 myapp:latestVerify Image Platforms
# Inspect image platforms
docker buildx imagetools inspect myregistry/myapp:latest
# Output shows:
# Name: myregistry/myapp:latest
# MediaType: application/vnd.docker.distribution.manifest.list.v2+json
# Digest: sha256:abc123...
# Manifests:
# Name: myregistry/myapp:latest@sha256:def456...
# MediaType: application/vnd.docker.distribution.manifest.v2+json
# Platform: linux/amd64
#
# Name: myregistry/myapp:latest@sha256:ghi789...
# MediaType: application/vnd.docker.distribution.manifest.v2+json
# Platform: linux/arm64CI/CD Integration
GitHub Actions
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push multi-platform
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=maxGitLab CI
build-multiplatform:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker run --privileged --rm tonistiigi/binfmt --install all
- docker buildx create --use
script:
- docker buildx build
--platform linux/amd64,linux/arm64
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--push
.Performance Considerations
Build Time
Multi-platform builds take longer:
- Single platform: ~2-3 minutes
- Multi-platform: ~4-6 minutes
Optimize with:
# Use GitHub Actions cache
cache-from: type=gha
cache-to: type=gha,mode=maxImage Size
Bun images are small on both platforms:
- AMD64: ~90MB (Alpine) / ~40MB (binary)
- ARM64: ~90MB (Alpine) / ~40MB (binary)
Troubleshooting
Build Fails on One Platform
# Build platforms separately to identify issues
docker buildx build --platform linux/amd64 -t myapp:amd64 .
docker buildx build --platform linux/arm64 -t myapp:arm64 .QEMU Performance
Cross-platform emulation is slow. For faster builds: 1. Use native builders for each architecture 2. Use remote builders (AWS Graviton for ARM, EC2 for AMD)
# Add remote builder
docker buildx create \
--name remote-arm64 \
--platform linux/arm64 \
ssh://user@arm64-hostRegistry Issues
Some registries don't support multi-platform manifests:
# Check if registry supports manifest lists
docker buildx imagetools inspect myregistry/myapp:latestAdvanced: Native Builders
Use separate machines for each architecture:
# AMD64 builder (local)
docker buildx create --name amd64-builder --platform linux/amd64
# ARM64 builder (remote Graviton instance)
docker buildx create \
--name arm64-builder \
--platform linux/arm64 \
--append ssh://user@graviton-host
# Use both builders
docker buildx use amd64-builder
docker buildx build --platform linux/amd64,linux/arm64 --push .Kubernetes Deployment
Multi-platform images work seamlessly in K8s:
spec:
containers:
- name: app
image: myregistry/myapp:latest # Pulls correct platform automaticallyKubernetes automatically selects the correct image variant based on node architecture.