## A basic server Containerizing a Deno app is straightforward. First, create a simple server app and run it. The following example slightly modified from the [online docs](https://docs.deno.com/runtime/fundamentals/http_server/). By convention, the entry point for your server app should be `main.ts`. `main.ts` ```ts const options = { port: Number(Deno.env.get("PORT")) || 8080, } Deno.serve(options, () => { return new Response("Hello, World!"); }); ``` Test it: ``` deno serve --allow-net --allow-env main.ts ``` Expected output: ``` Listening on http://0.0.0.0:8080/ ``` Good. Now let's run this locally in a Docker container. Deno just published an updated container image for the `2.0.0` release moments ago on [Docker Hub](https://hub.docker.com/r/denoland/deno) that can be used as the base image for creating a container for a Deno server app. In the top level directory of your Deno app, create a `Dockerfile` with the following contents: ```Dockerfile # This is based on Debian; for all the tagged variants, see: # https://hub.docker.com/r/denoland/deno/tags FROM denoland/deno:2.0.0 # Not necessary for cloud deployment; this is useful when publishing # ports (`--publish-all`) for testing locally. Exposed ports will then # be automatically assigned to available ports (typically in the range # 32768 - 61000). # Most cloud environments expect apps to listen on port 8080, but as a # best practice, apps should generally check the for the assigned port # (typically by reading the PORT environment variable). EXPOSE 8080 # This can be any directory you want, but using `app` is conventional. WORKDIR /app # Prefer not to run as root. USER deno # In general, deno.json changes less frequently than the rest of the # app source code, so optimize build time by caching dependencies in # this layer. If lockfile is present, fail if it's not up to date so # user can resolve. COPY deno.* . RUN ["/bin/deno", "install", "--frozen"] # For subsequent changes to the app, pre-compile and warm up the cache. # If lockfile is present, fail if it's not up to date so user can resolve. COPY . . RUN ["/bin/deno", "install", "--frozen", "--entrypoint", "main.ts"] # At this layer, run the server app. All dependencies should be cached and # the app has been pre-compiled for fast start. # Intentionally fail if dependencies aren't cached. CMD ["run", "--cached-only", "--allow-net", "--allow-env", "main.ts"] ``` Build a container image: ``` docker build -t helloworld . ``` Now run it: ``` docker run --rm -p 8080:8080 helloworld ``` The output running in a container should look the same as it did before. ## deno init --serve Start in another empty directory and run the following: ``` deno int --serve ``` This will scaffold a starter server project. However, we need to make changes to ensure that the server starts listening on the PORT specified by the hosting runtime environment. Modify `main.ts` so that it exports the `handler`. Change this line: ```ts const handler = route(routes, defaultHandler); ``` To: ```ts export const handler = route(routes, defaultHandler); ``` Create a `server.ts` file and add the following code: ```ts import {handler} from "./main.ts"; const options = { port: Number(Deno.env.get("PORT")) || 8080, } Deno.serve(options, handler); ``` Other than that, we basically left `main.ts` alone so that the generated test for its routes continue to work. Test the server: ``` deno run -NER server.ts ``` > The combined flags allow network (`-N`), environment (`-E`), and read (`-R`) access to serve over the network, read the `PORT` environment variable, and read files from the `static` directory to serve static content. > Since `main.ts` exports an HTTP route handler (which makes it easy to test without serving it, as shown in `main_test.ts`); you use the command `deno serve` when launching it. > > `deno serve` has a `--port` option, but we need programmatic control over the server creation so we can read from the `PORT` environment variable at runtime. Since `server.ts` starts a server for the HTTP route handler imported from `main.ts`, you need to use the command `deno run` when launching it. Using the same Dockerfile from before, we need to change the entrypoint from `main.ts` to `server.ts` in two places (`RUN` and `CMD`), as show below: ```Dockerfile # This is based on Debian; for all the tagged variants, see: # https://hub.docker.com/r/denoland/deno/tags FROM denoland/deno:2.0.0 # Not necessary for cloud deployment; this is useful when publishing # ports (`--publish-all`) for testing locally. Exposed ports will then # be automatically assigned to available ports (typically in the range # 32768 - 61000). # Most cloud environments expect apps to listen on port 8080, but as a # best practice, apps should generally check the for the assigned port # (typically by reading the PORT environment variable). EXPOSE 8080 # This can be any directory you want, but using `app` is conventional. WORKDIR /app # Prefer not to run as root. USER deno # In general, deno.json changes less frequently than the rest of the # app source code, so optimize build time by caching dependencies in # this layer. If lockfile is present, fail if it's not up to date so # user can resolve. COPY deno.* . RUN ["/bin/deno", "install", "--frozen"] # For subsequent changes to the app, pre-compile and warm up the cache. COPY . . RUN ["/bin/deno", "install", "--frozen", "--entrypoint", "server.ts"] # At this layer, run the server app. All dependencies should be cached and # the app has been pre-compiled for fast start. # Intentionally fail if dependencies aren't cached. CMD ["run", "--cached-only", "--allow-net", "--allow-env", "--allow-read", "server.ts"] ``` Build a container image: ``` docker build -t server . ``` Now run it: ``` docker run --rm -p 8080:8080 -e=PORT=8080 server ``` > In addition to mapping local port 8080 on the machine to internal container port 8080 (using the `-p` option), we also have to set the internal container `PORT` environment variable (using the `-e` option) so the app knows the port to listen on. > > If `8080` is already being used on your machine, you can publish on a different port, for example `9999`: > > `docker run --rm -p 9999:8080 -e=PORT=8080 server` Look at `main.ts` or `main_test.ts` to see which routes you can test in the browser, or just try the following links: [http://localhost:8080/](http://localhost:8080) [http://localhost:8080/users/foo](http://localhost:8080/users/foo) [http://localhost:8080/static/hello.js](http://localhost:8080/static/hello.js)