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
-
Permission Denied Errors
- Use
--allow-*flags appropriately - Consider using
deno.jsonpermissions
- Use
-
Large Image Sizes
- Use multi-stage builds
- Include only necessary files
- Add proper
.dockerignore
-
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 releasedenoland/deno:alpine- Alpine-based smaller imagedenoland/deno:distroless- Google's distroless-based imagedenoland/deno:ubuntu- Ubuntu-based imagedenoland/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:
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:
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:
- Build the image:
docker build -t my-deno-app . - 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:
- Create a build context structure:
project-root/
├── docker/
│ └── project-a/
│ ├── Dockerfile
│ ├── .dockerignore
│ └── build-context.sh
├── deno.json
├── project-a/
└── project-b/
- Create a
.dockerignore:
*
!deno.json
!project-a/**
!project-b/** # Only if needed
- Create a build context script:
#!/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
- Create a minimal 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"]
- 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.jsonfile - Maintain the same directory structure as development
- Document workspace dependencies clearly
- Use build scripts to manage context
- Include only required workspace members
- Update
.dockerignorewhen dependencies change