Deploy Deno applications with Docker containers and production optimization

Intermediate 35 min Apr 06, 2026 50 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Learn how to containerize Deno applications with Docker using multi-stage builds, security hardening, and production-ready configuration. This tutorial covers creating optimized Dockerfiles, implementing proper caching strategies, and deploying with Docker Compose for scalable web applications.

Prerequisites

  • Root or sudo access
  • At least 2GB RAM
  • 10GB free disk space
  • Internet connection for downloading Docker and Deno

What this solves

Containerizing Deno applications provides consistent deployment environments across development, staging, and production systems. Docker containers eliminate the "it works on my machine" problem by packaging your Deno runtime, dependencies, and application code into portable, isolated units that run identically everywhere.

This approach is essential for teams deploying modern web applications that need predictable scaling, automated deployments, and infrastructure-as-code practices. You'll learn to create production-optimized containers with proper security boundaries and resource management.

Step-by-step installation

Update system packages and install Docker

Start by updating your package manager and installing Docker Engine with the official repository for the latest stable version.

sudo apt update && sudo apt upgrade -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo dnf update -y
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Configure Docker service and user permissions

Enable Docker to start automatically on boot and add your user to the docker group to run commands without sudo.

sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker
docker --version

Install Deno for local development

Install Deno on your host system for development and testing before containerization.

curl -fsSL https://deno.land/install.sh | sh
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
deno --version

Create a sample Deno web application

Create a directory structure for your Deno application with a simple HTTP server that we'll containerize.

mkdir -p ~/deno-docker-app/src
cd ~/deno-docker-app
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";

const port = parseInt(Deno.env.get("PORT") || "8000");

const handler = (request: Request): Response => {
  const url = new URL(request.url);
  
  if (url.pathname === "/health") {
    return new Response(JSON.stringify({ status: "healthy", timestamp: Date.now() }), {
      headers: { "content-type": "application/json" },
    });
  }
  
  if (url.pathname === "/api/info") {
    return new Response(JSON.stringify({
      message: "Deno Docker Application",
      version: "1.0.0",
      deno: Deno.version.deno,
      env: Deno.env.get("ENVIRONMENT") || "development"
    }), {
      headers: { "content-type": "application/json" },
    });
  }
  
  return new Response("Welcome to Deno Docker App!", {
    headers: { "content-type": "text/plain" },
  });
};

console.log(HTTP server running on port ${port});
await serve(handler, { port });

Create production-optimized Dockerfile with multi-stage build

Build a multi-stage Dockerfile that creates a minimal production image with security hardening and dependency caching.

# Build stage
FROM denoland/deno:1.38.3 AS builder

Set working directory

WORKDIR /app

Copy dependency files first for better caching

COPY src/deps.ts ./src/

Cache dependencies

RUN deno cache src/deps.ts

Copy source code

COPY src/ ./src/

Cache and compile the application

RUN deno cache src/main.ts RUN deno compile --allow-net --allow-env --output=server src/main.ts

Production stage

FROM debian:12-slim AS production

Install ca-certificates for HTTPS requests

RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*

Create non-root user for security

RUN groupadd -r denoapp && useradd -r -g denoapp -s /bin/false denoapp

Create app directory with correct permissions

RUN mkdir -p /app && chown denoapp:denoapp /app

Copy compiled binary from builder stage

COPY --from=builder --chown=denoapp:denoapp /app/server /app/server

Switch to non-root user

USER denoapp

Set working directory

WORKDIR /app

Expose port

EXPOSE 8000

Health check

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1

Run the server

CMD ["./server"]

Create dependency management file

Create a dedicated deps.ts file to centralize and cache external dependencies efficiently.

// HTTP server dependencies
export { serve } from "https://deno.land/std@0.208.0/http/server.ts";

// Additional utilities
export { parse } from "https://deno.land/std@0.208.0/flags/mod.ts";

Update main.ts to use deps.ts

Modify the main application file to import from the centralized dependencies file.

import { serve } from "./deps.ts";

const port = parseInt(Deno.env.get("PORT") || "8000");

const handler = (request: Request): Response => {
  const url = new URL(request.url);
  
  if (url.pathname === "/health") {
    return new Response(JSON.stringify({ status: "healthy", timestamp: Date.now() }), {
      headers: { "content-type": "application/json" },
    });
  }
  
  if (url.pathname === "/api/info") {
    return new Response(JSON.stringify({
      message: "Deno Docker Application",
      version: "1.0.0",
      deno: Deno.version.deno,
      env: Deno.env.get("ENVIRONMENT") || "development",
      uptime: performance.now()
    }), {
      headers: { "content-type": "application/json" },
    });
  }
  
  return new Response("Welcome to Deno Docker App!", {
    headers: { "content-type": "text/plain" },
  });
};

console.log(HTTP server running on port ${port});
await serve(handler, { port });

Create Docker Compose configuration

Set up Docker Compose for production deployment with environment variables, health checks, and restart policies.

version: '3.8'

services:
  deno-app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    container_name: deno-production-app
    ports:
      - "8000:8000"
    environment:
      - PORT=8000
      - ENVIRONMENT=production
      - NODE_ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp:size=100M,noexec,nosuid,nodev
    ulimits:
      nproc: 65535
      nofile:
        soft: 65535
        hard: 65535
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - deno-network

networks:
  deno-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Create .dockerignore for build optimization

Exclude unnecessary files from the Docker build context to reduce image size and build time.

node_modules
.git
.gitignore
README.md
.env*
*.log
docker-compose.yml
.dockerignore
Dockerfile
.vscode
.idea
*.md
logs/
temp/
*.tmp
.DS_Store

Build and run the containerized application

Build the Docker image and start the application using Docker Compose with production optimizations.

docker compose build --no-cache
docker compose up -d
docker compose ps

Configure production environment variables

Create an environment file for production secrets and configuration management.

PORT=8000
ENVIRONMENT=production
LOG_LEVEL=info
TZ=UTC

Security headers

SECURE_HEADERS=true CORS_ORIGIN=https://example.com

Resource limits

MAX_CONNECTIONS=1000 REQUEST_TIMEOUT=30000
Never commit .env files to version control. Add .env* to your .gitignore file to prevent accidentally exposing sensitive configuration data.

Set up production monitoring and logging

Configure structured logging and health monitoring for production deployments.

version: '3.8'

services:
  deno-app:
    extends:
      file: docker-compose.yml
      service: deno-app
    env_file:
      - .env.production
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.deno-app.rule=Host(example.com)"
      - "traefik.http.routers.deno-app.tls=true"
      - "traefik.http.services.deno-app.loadbalancer.server.port=8000"

  prometheus:
    image: prom/prometheus:latest
    container_name: deno-prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    networks:
      - deno-network

networks:
  deno-network:
    external: true

Verify your setup

Test that your containerized Deno application is running correctly and responding to requests.

docker compose logs deno-app
curl -i http://localhost:8000
curl -i http://localhost:8000/health
curl -i http://localhost:8000/api/info
docker compose exec deno-app ps aux

Check resource usage and container security:

docker stats deno-production-app
docker inspect deno-production-app | grep -A 5 SecurityOpt
docker compose top

Production optimization and security hardening

Implement resource limits and monitoring

Add CPU and memory limits to prevent resource exhaustion and configure monitoring endpoints.

let requestCount = 0;
let errorCount = 0;
const startTime = Date.now();

export function incrementRequestCount() {
  requestCount++;
}

export function incrementErrorCount() {
  errorCount++;
}

export function getMetrics() {
  return {
    requests_total: requestCount,
    errors_total: errorCount,
    uptime_seconds: Math.floor((Date.now() - startTime) / 1000),
    memory_usage: Deno.memoryUsage(),
  };
}

Add security headers and rate limiting

Enhance the application with security headers and basic rate limiting for production use.

const rateLimitMap = new Map();

export function getSecurityHeaders(): Record {
  return {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "X-XSS-Protection": "1; mode=block",
    "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Content-Security-Policy": "default-src 'self'",
  };
}

export function rateLimit(clientIP: string, limit = 100, windowMs = 60000): boolean {
  const now = Date.now();
  const client = rateLimitMap.get(clientIP);
  
  if (!client || now > client.resetTime) {
    rateLimitMap.set(clientIP, { count: 1, resetTime: now + windowMs });
    return true;
  }
  
  if (client.count >= limit) {
    return false;
  }
  
  client.count++;
  return true;
}

Common issues

SymptomCauseFix
Permission denied errorsContainer running as root or wrong file ownershipUse non-root USER in Dockerfile and chown denoapp:denoapp for app files
Slow container startupDependencies downloaded at runtimeCache dependencies in build stage with deno cache
Large image sizeUsing full Deno image in productionUse multi-stage build with minimal base image like debian:slim
Health check failuresMissing curl in production imageInstall ca-certificates and add curl to Dockerfile or use Deno's fetch
Out of memory errorsNo resource limits setAdd memory limits in docker-compose.yml deploy section
Cannot connect to external APIsMissing CA certificatesInstall ca-certificates package in production stage

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.