Deployment and operations of Deno are important aspects of building production-grade applications. Understanding how to properly deploy and operate Deno applications can ensure application stability and maintainability.
Deployment Overview
Deno applications can be deployed to various environments, including traditional servers, containerized platforms, cloud services, and edge computing platforms.
Docker Deployment
1. Basic Dockerfile
dockerfile# Use official Deno image FROM denoland/deno:1.38.0 # Set working directory WORKDIR /app # Copy dependency files COPY deno.json ./ # Cache dependencies RUN deno cache src/main.ts # Copy source code COPY . . # Expose port EXPOSE 8000 # Run application CMD ["deno", "run", "--allow-net", "--allow-env", "src/main.ts"]
2. Multi-stage Build
dockerfile# Build stage FROM denoland/deno:1.38.0 AS builder WORKDIR /app COPY . . # Compile to executable RUN deno compile --allow-net --allow-env --output=app src/main.ts # Run stage FROM debian:bullseye-slim WORKDIR /app # Copy executable from build stage COPY /app/app . # Expose port EXPOSE 8000 # Run application CMD ["./app"]
3. Production Dockerfile
dockerfile# Build stage FROM denoland/deno:1.38.0 AS builder WORKDIR /app # Install dependencies COPY deno.json ./ RUN deno cache src/main.ts # Copy source code COPY . . # Run tests RUN deno test --allow-all # Compile to executable RUN deno compile \ --allow-net \ --allow-env \ --allow-read \ --output=app \ src/main.ts # Run stage FROM debian:bullseye-slim WORKDIR /app # Install necessary runtime dependencies RUN apt-get update && \ apt-get install -y ca-certificates && \ rm -rf /var/lib/apt/lists/* # Create non-root user RUN useradd -m -u 1000 deno # Copy executable from build stage COPY /app/app . # Change owner RUN chown -R deno:deno /app # Switch to non-root user USER deno # Expose port EXPOSE 8000 # Health check HEALTHCHECK \ CMD curl -f http://localhost:8000/health || exit 1 # Run application CMD ["./app"]
Kubernetes Deployment
1. Deployment Configuration
yamlapiVersion: apps/v1 kind: Deployment metadata: name: deno-app labels: app: deno-app spec: replicas: 3 selector: matchLabels: app: deno-app template: metadata: labels: app: deno-app spec: containers: - name: deno-app image: your-registry/deno-app:latest ports: - containerPort: 8000 env: - name: PORT value: "8000" - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8000 initialDelaySeconds: 5 periodSeconds: 5
2. Service Configuration
yamlapiVersion: v1 kind: Service metadata: name: deno-app-service spec: selector: app: deno-app ports: - protocol: TCP port: 80 targetPort: 8000 type: LoadBalancer
3. ConfigMap and Secret
yaml# ConfigMap apiVersion: v1 kind: ConfigMap metadata: name: app-config data: PORT: "8000" LOG_LEVEL: "info" --- # Secret apiVersion: v1 kind: Secret metadata: name: app-secrets type: Opaque data: database-url: <base64-encoded-url> api-key: <base64-encoded-key>
Cloud Platform Deployment
1. Deno Deploy
Deno Deploy is Deno's official edge computing platform.
typescript// main.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/") { return new Response("Hello from Deno Deploy!", { headers: { "Content-Type": "text/plain" }, }); } return new Response("Not Found", { status: 404 }); }; await serve(handler, { port: 8000 });
Deployment steps:
- Create Deno Deploy account
- Connect GitHub repository
- Configure deployment settings
- Automatic deployment
2. Vercel Deployment
json// vercel.json { "version": 2, "builds": [ { "src": "src/main.ts", "use": "@vercel/deno" } ], "routes": [ { "src": "/(.*)", "dest": "/src/main.ts" } ] }
3. Railway Deployment
toml# railway.toml [build] builder = "NIXPACKS" [deploy] startCommand = "deno run --allow-net --allow-env src/main.ts" [env] PORT = "8000"
Process Management
1. Using PM2
javascript// ecosystem.config.js module.exports = { apps: [{ name: 'deno-app', script: 'deno', args: 'run --allow-net --allow-env src/main.ts', instances: 'max', exec_mode: 'cluster', autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'production', PORT: 8000 } }] };
bash# Install PM2 npm install -g pm2 # Start application pm2 start ecosystem.config.js # View status pm2 status # View logs pm2 logs # Restart application pm2 restart deno-app # Stop application pm2 stop deno-app
2. Using Systemd
ini# /etc/systemd/system/deno-app.service [Unit] Description=Deno Application After=network.target [Service] Type=simple User=deno WorkingDirectory=/app Environment="PORT=8000" Environment="DATABASE_URL=postgres://..." ExecStart=/usr/local/bin/deno run --allow-net --allow-env /app/src/main.ts Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
bash# Enable service sudo systemctl enable deno-app # Start service sudo systemctl start deno-app # View status sudo systemctl status deno-app # View logs sudo journalctl -u deno-app -f # Restart service sudo systemctl restart deno-app
Monitoring and Logging
1. Log Management
typescript// logger.ts import { getLogger, setup, handlers } from "https://deno.land/std@0.208.0/log/mod.ts"; await setup({ handlers: { console: new handlers.ConsoleHandler("INFO"), file: new handlers.FileHandler("INFO", { filename: "./logs/app.log", formatter: "{levelName} {datetime} {msg}", }), }, loggers: { default: { level: "INFO", handlers: ["console", "file"], }, }, }); export const logger = getLogger();
2. Health Check
typescript// health.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; let isHealthy = true; let isReady = false; // Simulate startup time setTimeout(() => { isReady = true; }, 5000); const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/health") { return new Response(JSON.stringify({ status: isHealthy ? "ok" : "error" }), { headers: { "Content-Type": "application/json" }, status: isHealthy ? 200 : 503, }); } if (url.pathname === "/ready") { return new Response(JSON.stringify({ ready: isReady }), { headers: { "Content-Type": "application/json" }, status: isReady ? 200 : 503, }); } return new Response("Not Found", { status: 404 }); }; await serve(handler, { port: 8000 });
3. Metrics Collection
typescript// metrics.ts class MetricsCollector { private metrics: Map<string, number> = new Map(); private counters: Map<string, number> = new Map(); increment(name: string, value: number = 1) { const current = this.counters.get(name) || 0; this.counters.set(name, current + value); } gauge(name: string, value: number) { this.metrics.set(name, value); } timing(name: string, duration: number) { const timings = this.metrics.get(`${name}_timings`) || []; timings.push(duration); this.metrics.set(`${name}_timings`, timings); } getMetrics(): Record<string, any> { return { counters: Object.fromEntries(this.counters), gauges: Object.fromEntries(this.metrics), }; } } export const metrics = new MetricsCollector();
Performance Optimization
1. Connection Pool
typescript// connection-pool.ts class ConnectionPool<T> { private pool: T[] = []; private maxConnections: number; private factory: () => Promise<T>; constructor(maxConnections: number, factory: () => Promise<T>) { this.maxConnections = maxConnections; this.factory = factory; } async acquire(): Promise<T> { if (this.pool.length > 0) { return this.pool.pop()!; } return await this.factory(); } release(connection: T) { if (this.pool.length < this.maxConnections) { this.pool.push(connection); } } }
2. Caching Strategy
typescript// cache.ts class Cache { private cache: Map<string, { value: any; expires: number }> = new Map(); private ttl: number; constructor(ttl: number = 60000) { this.ttl = ttl; } set(key: string, value: any, ttl?: number) { const expires = Date.now() + (ttl || this.ttl); this.cache.set(key, { value, expires }); } get(key: string): any | null { const item = this.cache.get(key); if (!item) { return null; } if (Date.now() > item.expires) { this.cache.delete(key); return null; } return item.value; } clear() { this.cache.clear(); } cleanup() { const now = Date.now(); for (const [key, item] of this.cache.entries()) { if (now > item.expires) { this.cache.delete(key); } } } }
Security Best Practices
1. Environment Variable Management
typescript// config.ts interface Config { port: number; databaseUrl: string; apiKey: string; logLevel: string; } function loadConfig(): Config { const port = parseInt(Deno.env.get("PORT") || "8000"); const databaseUrl = Deno.env.get("DATABASE_URL"); const apiKey = Deno.env.get("API_KEY"); const logLevel = Deno.env.get("LOG_LEVEL") || "info"; if (!databaseUrl) { throw new Error("DATABASE_URL environment variable is required"); } if (!apiKey) { throw new Error("API_KEY environment variable is required"); } return { port, databaseUrl, apiKey, logLevel, }; } export const config = loadConfig();
2. Principle of Least Privilege
bash# Grant only necessary permissions deno run --allow-net --allow-env src/main.ts # Limit network access deno run --allow-net=api.example.com src/main.ts # Limit file access deno run --allow-read=/app/data src/main.ts
CI/CD Integration
1. GitHub Actions
yaml# .github/workflows/deploy.yml name: Deploy on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Deno uses: denoland/setup-deno@v1 with: deno-version: v1.38.0 - name: Run tests run: deno test --allow-all - name: Lint run: deno lint - name: Format check run: deno fmt --check deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build Docker image run: docker build -t deno-app:${{ github.sha }} . - name: Push to registry run: | echo ${{ secrets.REGISTRY_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin docker push deno-app:${{ github.sha }}
2. GitLab CI
yaml# .gitlab-ci.yml stages: - test - build - deploy test: stage: test image: denoland/deno:1.38.0 script: - deno test --allow-all - deno lint - deno fmt --check build: stage: build image: docker:latest services: - docker:dind script: - docker build -t deno-app:$CI_COMMIT_SHA . - docker push deno-app:$CI_COMMIT_SHA deploy: stage: deploy image: alpine:latest script: - kubectl set image deployment/deno-app deno-app=deno-app:$CI_COMMIT_SHA only: - main
Troubleshooting
1. Common Issues
Issue: Application fails to start
bash# Check logs deno run --log-level=debug src/main.ts # Check permissions deno info src/main.ts
Issue: Memory leak
typescript# Periodically check memory usage setInterval(() => { const usage = Deno.memoryUsage(); console.log("Memory usage:", usage); }, 60000);
Issue: Performance degradation
typescript# Use performance profiling import { performance } from "https://deno.land/std@0.208.0/node/performance.ts"; const start = performance.now(); // Execute operation const duration = performance.now() - start; console.log(`Operation took ${duration}ms`);
Best Practices
- Containerized deployment: Use Docker to ensure environment consistency
- Health checks: Implement health check endpoints
- Logging: Record key operations and errors
- Monitoring metrics: Collect and monitor application metrics
- Automated deployment: Use CI/CD for automated deployment
- Principle of least privilege: Grant only necessary permissions
- Resource limits: Set reasonable resource limits
- Backup strategy: Regularly backup important data
Deployment and operations of Deno require considering multiple aspects. Through proper planning and implementation, stable and reliable production-grade applications can be built.