## 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)