close
Skip to main content
On this page

Deno and Docker

Using Deno with Docker Jump to heading

Deno provides official Docker files and images.

To use the official image, create a Dockerfile in your project directory:

FROM denoland/deno:latest

WORKDIR /app

# Copy manifests first so the dependency install layer caches across
# source-only edits
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod --skip-types

# Then copy the rest of the source
COPY . .

CMD ["deno", "run", "--allow-net", "main.ts"]

deno ci performs a reproducible install from deno.lock. --prod skips devDependencies, and --skip-types drops @types/* packages — both shrink the resulting image without affecting runtime behavior.

Best Practices Jump to heading

Use Multi-stage Builds Jump to heading

For smaller production images:

# Build stage
FROM denoland/deno:latest AS builder
# Point Deno's cache at a known location so it can be copied to the next stage
ENV DENO_DIR=/deno-dir
WORKDIR /app

# Copy manifests first so the dependency install layer caches across
# source-only edits
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod --skip-types

# Then copy the rest of the source
COPY . .

# Production stage
FROM denoland/deno:latest
ENV DENO_DIR=/deno-dir
WORKDIR /app
COPY --from=builder /app .
# Copy the populated Deno cache so the runtime stage has the dependencies
COPY --from=builder /deno-dir /deno-dir
CMD ["deno", "run", "--allow-net", "main.ts"]

Without copying $DENO_DIR, deno ci only writes to Deno's global cache inside the builder stage — those files do not travel with COPY --from=builder /app ., so the container re-downloads dependencies on first run.

Permission Flags Jump to heading

Specify required permissions explicitly:

CMD ["deno", "run", "--allow-net=api.example.com", "--allow-read=/data", "main.ts"]

Development Container Jump to heading

For development with hot-reload:

FROM denoland/deno:latest

WORKDIR /app
COPY . .

CMD ["deno", "run", "--watch", "--allow-net", "main.ts"]

Common Issues and Solutions Jump to heading

  1. Permission Denied Errors

    • Use --allow-* flags appropriately
    • Consider using deno.json permissions
  2. Large Image Sizes

    • Use multi-stage builds
    • Include only necessary files
    • Add proper .dockerignore
  3. Cache Invalidation

    • Separate dependency caching
    • Use proper layer ordering

Example .dockerignore Jump to heading

.git
.gitignore
Dockerfile
README.md
*.log
_build/
node_modules/

Available Docker Tags Jump to heading

Deno provides several official tags:

  • denoland/deno:latest - Latest stable release
  • denoland/deno:alpine - Alpine-based smaller image
  • denoland/deno:distroless - Google's distroless-based image
  • denoland/deno:ubuntu - Ubuntu-based image
  • denoland/deno:1.x - Specific version tags

Environment Variables Jump to heading

Common environment variables for Deno in Docker:

ENV DENO_DIR=/deno-dir/
ENV DENO_INSTALL_ROOT=/usr/local
ENV PATH=${DENO_INSTALL_ROOT}/bin:${PATH}

# Optional environment variables
ENV DENO_NO_UPDATE_CHECK=1
ENV DENO_NO_PROMPT=1

Running Tests in Docker Jump to heading

FROM denoland/deno:latest

WORKDIR /app
COPY . .

# Run tests
CMD ["deno", "test", "--allow-none"]

Using Docker Compose Jump to heading

A basic Compose file for development with hot-reload:

docker-compose.yml
services:
  deno-app:
    build: .
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    environment:
      - DENO_ENV=development
    command: ["deno", "run", "--watch", "--allow-net", "main.ts"]

For a more realistic setup with a database:

docker-compose.yml
services:
  app:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgres://deno:${POSTGRES_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    command:
      [
        "deno",
        "run",
        "--allow-net=db:5432",
        "--allow-env=DATABASE_URL",
        "main.ts",
      ]

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: deno
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U deno"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

volumes:
  pgdata:

Put secrets like POSTGRES_PASSWORD in a .env file next to docker-compose.yml. Compose loads it automatically. Don't commit it.

Start the services with docker compose up, or run in the background with docker compose up -d.

Health Checks Jump to heading

HEALTHCHECK --interval=30s --timeout=3s \
  CMD deno eval "try { await fetch('http://localhost:8000/health'); } catch { Deno.exit(1); }"

Common Development Workflow Jump to heading

For local development:

  1. Build the image: docker build -t my-deno-app .
  2. Run with volume mount:
>_
docker run -it --rm \
  -v ${PWD}:/app \
  -p 8000:8000 \
  my-deno-app

Security Considerations Jump to heading

  • Run as non-root user:
# Create deno user
RUN addgroup --system deno && \
    adduser --system --ingroup deno deno

# Switch to deno user
USER deno

# Continue with rest of Dockerfile
  • Use minimal permissions:
CMD ["deno", "run", "--allow-net=api.example.com", "--allow-read=/app", "main.ts"]
  • Consider using --deny-* flags for additional security

Working with Workspaces in Docker Jump to heading

When working with Deno workspaces (monorepos) in Docker, there are two main approaches:

1. Full Workspace Containerization Jump to heading

Include the entire workspace when you need all dependencies:

FROM denoland/deno:latest

WORKDIR /app

# Copy entire workspace
COPY deno.json .
COPY project-a/ ./project-a/
COPY project-b/ ./project-b/

WORKDIR /app/project-a
CMD ["deno", "run", "-A", "mod.ts"]

2. Minimal Workspace Containerization Jump to heading

For smaller images, include only required workspace members:

  1. Create a build context structure:
>_
project-root/
├── docker/
│   └── project-a/
│       ├── Dockerfile
│       ├── .dockerignore
│       └── build-context.sh
├── deno.json
├── project-a/
└── project-b/
  1. Create a .dockerignore:
docker/project-a/.dockerignore
*
!deno.json
!project-a/**
!project-b/**  # Only if needed
  1. Create a build context script:
docker/project-a/build-context.sh
#!/bin/bash

# Create temporary build context
BUILD_DIR="./tmp-build-context"
mkdir -p $BUILD_DIR

# Copy workspace configuration
cp ../../deno.json $BUILD_DIR/

# Copy main project
cp -r ../../project-a $BUILD_DIR/

# Copy only required dependencies
if grep -q "\"@scope/project-b\"" "../../project-a/mod.ts"; then
    cp -r ../../project-b $BUILD_DIR/
fi
  1. Create a minimal Dockerfile:
docker/project-a/Dockerfile
FROM denoland/deno:latest

WORKDIR /app

# Copy only necessary files
COPY deno.json .
COPY project-a/ ./project-a/
# Copy dependencies only if required
COPY project-b/ ./project-b/

WORKDIR /app/project-a

CMD ["deno", "run", "-A", "mod.ts"]
  1. Build the container:
>_
cd docker/project-a
./build-context.sh
docker build -t project-a -f Dockerfile tmp-build-context
rm -rf tmp-build-context

Best Practices Jump to heading

  • Always include the root deno.json file
  • Maintain the same directory structure as development
  • Document workspace dependencies clearly
  • Use build scripts to manage context
  • Include only required workspace members
  • Update .dockerignore when dependencies change

Last updated on

Did you find what you needed?

Privacy policy