Self-hosted service
denoland/deno_docker avatar
denoland/deno_docker

deno_docker, five Deno base images and a version number that lives in the tag

Latest dockerfiles and images for Deno - alpine, centos, debian, ubuntu

1,018 stars116 forksDockerfileMIT

At a glance

What is it?
The deno_docker repository holds the Dockerfiles behind the official Deno images, published as Alpine, Debian, Distroless, Ubuntu and bare binary variants on Docker Hub and GHCR. The interesting details are the caching recipe in the sample Dockerfile, the DENO_DIR workaround for Google Cloud Run, and the fact that the GitHub release tags stopped in 2021 while the images are still versioned.
Who is it for?
deno_docker fits a team shipping Deno services in containers who want a maintained base image instead of a hand-built one, and the Debian tag is the default for a reason, since it is the variant the documented run and shell commands assume.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Dockerfile, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Five variants, two registries, one version number

The repository is small by design: it holds the Dockerfiles for the official Deno images, and the README lists the five published variants. There is denoland/deno:alpine for Alpine Linux, denoland/deno:debian for Debian, which is the default, denoland/deno:distroless, denoland/deno:ubuntu, and denoland/deno:bin, which contains only the binary.

The repository layout maps onto that list one file per variant, with alpine.dockerfile, bin.dockerfile, debian.dockerfile, distroless.dockerfile and ubuntu.dockerfile at the root, plus an _entry.sh, a.dockerignore, a.bmp.yml, a tools/ directory and an example/ directory. The naming convention is worth noting because it makes the build inputs obvious: a change to one base image is a change to one file, and there is no single Dockerfile that branches on a build argument.

Images are published to two registries, Docker Hub and GitHub Container Registry, and the same tag works in both:

bash
docker pull denoland/deno:2.9.7        # Docker Hub
docker pull ghcr.io/denoland/deno:2.9.7 # GHCR

The two-registry arrangement matters for organisations that pull from a mirrored or internal registry, since it gives you a second source without changing the tag.

One inconsistency is worth flagging. The release history for the repository shows tags 1.16.1, 1.16.2 and 1.16.4, all from late 2021, while the versions in the README are 2.9.x. In other words, the version you run is expressed in the image tag, not in a GitHub release, so anyone tracking releases to find out what changed will be reading a page that stopped updating years ago.

Running the repl, a shell, or your own file

Three commands cover most of what you do with the image, and the third is the one you will actually use in a project.

To start the Deno repl interactively:

bash
docker run -it denoland/deno:2.9.7 repl

To shell into the container instead, which is what you want when you are debugging a missing tool:

bash
docker run -it denoland/deno:2.9.7 sh

To run a TypeScript file from your working directory, the image needs the directory mounted and the permissions granted:

bash
docker run -it -p 1993:1993 -v $PWD:/app denoland/deno:2.9.7 run --allow-net /app/main.ts

The three flags are the whole contract. The -p flag maps port 1993 on the container to the same port on the host. The -v flag mounts the host working directory at /app in the container, which is what makes main.ts visible to the runtime. And the arguments after the image name are passed to Deno itself, so --allow-net is a permission the Deno process is given, not a Docker option.

That last point is the part people get wrong on the first attempt. The permission flags belong to the command inside the container, so the order matters: options for Docker come before the image name, and everything after it goes to Deno.

The sample Dockerfile puts the cache in the right layers

The example directory at the root of the repository contains a Dockerfile that is worth reading as a template, because the ordering of its steps is the whole point:

dockerfile
FROM denoland/deno:2.9.7

# The port that your application listens to.
EXPOSE 1993

WORKDIR /app

# Cache dependencies as a layer (re-run only when package.json changes).
COPY package.json .
RUN deno install

# These steps will be re-run upon each file change in your working directory:
COPY . .
# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache main.ts

# Prefer not to run as root.
USER deno

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

The dependency layer is separated from the source layer. Only package.json is copied before deno install runs, so that layer is rebuilt when the manifest changes and not on every code edit. The full source is copied afterwards, and deno cache main.ts pre-compiles the entry point so the work is not repeated at each startup.

Two smaller decisions matter as much. The image switches to the deno user before the entry point, and the README notes that the Dockerfiles provide a USER deno and set DENO_DIR to /deno-dir/, which can be overridden. That combination is why a mounted volume for the cache is the right pattern for a multi-instance deployment.

The permission set in the CMD is the minimum this example needs: network, read and environment access. It is worth noticing that a container image gives you a security boundary and Deno gives you a permission system inside it, and the example uses the second without touching the first.

The bin tag is a file copy, not an installation

If you would rather build on your own base image, the bin variant exists for exactly that. The README calls it a way to simplify the process, and the mechanism is a single copy instruction:

dockerfile
FROM ubuntu
COPY --from=denoland/deno:bin-2.9.7 /deno /usr/local/bin/deno

That is the whole trick. There is no package manager, no runtime dependency installation and no shell wrapper, because the bin image contains one file, the Deno executable, and the copy instruction moves it into /usr/local/bin on your base.

The trade is that you take on the compatibility work. The official variants are built on a known base with a known set of libraries, and picking your own base means deciding whether that base has what Deno needs, which the README does not enumerate for you. It also means your image is now two images, the base and the copied binary, and the version of Deno inside it is pinned by the tag on the bin image.

For a hardened or minimal image where you want full control of what is present, this is the variant to reach for. For a project that just wants to run a service, the Debian tag with the documented layering is less work.

DENO_DIR and the Google Cloud Run caching conflict

One platform-specific note in the README is worth repeating, because the symptom it describes is confusing. Because of conflicts with the Google Cloud Run caching mechanism, it is required to use a different path for DENO_DIR in your Dockerfile:

dockerfile
# set DENO_DIR to avoid conflicts with google cloud
ENV DENO_DIR=./.deno_cache

The stated failure without it is that the Cloud Run instance tries to download dependencies every time. And when you run with --cached-only, you get the error "Specified not found in cache".

The fix is to point DENO_DIR at a path inside the working directory instead, which makes the cache part of the same layer the platform is already caching.

The same ENV instruction generalises beyond Cloud Run. Anywhere the container filesystem is treated as disposable between runs, a cache in /deno-dir/ is a cache that does not survive, and the same env line plus a mounted volume is the fix. The README also notes that if you run multiple Deno instances within the same image, you can mount the cache directory as a shared volume, which is the arrangement to use when a container runs more than one process.

The shell alias: deno as a function around docker run

The last convenience in the README is a shell function that makes deno feel like a local command while everything happens in a container. You add it to your shell init file, for example .bashrc:

bash
deno () {
  docker run \
    --interactive \
    --tty \
    --rm \
    --volume $PWD:/app \
    --volume $HOME/.deno:/deno-dir \
    --workdir /app \
    denoland/deno:2.9.7 \
    "$@"
}

and then in your terminal:

bash
source ~/.bashrc
deno --version
deno run ./main.ts

The function passes through every argument, mounts the working directory at /app and mounts $HOME/.deno at /deno-dir, which is the path the images use for the cache. So the dependencies you download on the first run are still there on the second, and the two mounts match the two paths the image expects.

It is worth being clear about what this is. The deno command on your machine is still not Deno, it is a container that runs Deno, and the difference shows up in every way that matters to a developer: a container start on every invocation, the file and network behaviour of a container boundary, and the fact that a process which writes outside the mounted paths will not see its own state on the next run.

For a developer who wants the runtime consistent across machines and is happy with that overhead, it is a reasonable trade. For a script that calls deno many times, the startup cost is paid repeatedly, and the Dockerfile path in the README is the better answer.

Editorial conclusion

deno_docker fits a team shipping Deno services in containers who want a maintained base image instead of a hand-built one, and the Debian tag is the default for a reason, since it is the variant the documented run and shell commands assume. It does not fit a workflow that needs reproducible builds by digest, because the documentation teaches tagging by version, and it does not fit a Google Cloud Run deployment that ignores the DENO_DIR instruction, since the documented failure mode is a service that re-downloads dependencies on every cold start. Before adopting an image, decide which variant matches the tooling you need inside the container, and set DENO_DIR explicitly rather than inheriting the default of /deno-dir/ if your platform caches the layer in a way that conflicts. The licence is MIT and the last push was on 2026-09-17.

Frequently asked questions

Which Deno Docker image tags are published?

Five: denoland/deno:alpine, denoland/deno:debian which is the default, denoland/deno:distroless, denoland/deno:ubuntu and denoland/deno:bin which contains only the binary. Each is available from Docker Hub and from GHCR under the same tag.

How do I run a Deno script from a local directory in a container?

Mount the working directory and pass the permissions to Deno after the image name, for example docker run -it -p 1993:1993 -v $PWD:/app denoland/deno:2.9.7 run --allow-net /app/main.ts. The -p and -v flags are Docker options and must come before the image name, while --allow-net is a Deno permission.

Why does my Google Cloud Run service download dependencies on every start?

Because of the conflict with the platform's caching mechanism, the README says to set DENO_DIR to a different path, for example ENV DENO_DIR=./.deno_cache. Without it the instance tries to download dependencies every time, and running with --cached-only produces the error "Specified not found in cache".

Can I use Deno on my own base image?

Yes. The denoland/deno:bin image contains only the binary, and the README shows copying it into a base of your choice with COPY --from=denoland/deno:bin-2.9.7 /deno /usr/local/bin/deno.

Where is the Deno cache inside the container, and how do I persist it?

The Dockerfiles provide a USER deno and set DENO_DIR to /deno-dir/, which can be overridden. The README notes that if you run multiple Deno instances within the same image you can mount that directory as a shared volume.

Official sources

  1. denoland/deno_docker on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/denoland-deno-docker.svg)](https://hysenlabs.com/projects/denoland-deno-docker)