Deploy Deno applications with Docker containers and production optimization

Intermediate 35 min Apr 06, 2026 938 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

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

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

Rotterdam 01:25 · reachable in a message, no ticket form