Using the main Dockerfile instructions
The Dockerfile syntax is very simple. You've already seen FROM, COPY, USER, RUN, and CMD, which are enough to package up a basic application to run as a container. For real-world images you'll need to do more than that, and there are three more key instructions to understand.
Here's a Dockerfile for a simple static website; it uses Internet Information Services (IIS) and serves an HTML page on the default website, which shows some basic details:
# escape=`
FROM mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
SHELL ["powershell"]
ARG ENV_NAME=DEV
EXPOSE 80
COPY template.html C:\template.html
RUN (Get-Content -Raw -Path C:\template.html) `
-replace '{hostname}', [Environment]::MachineName `
-replace '{environment}', [Environment]::GetEnvironmentVariable('ENV_NAME') `
| Set-Content -Path C:\inetpub\wwwroot\index.html
This Dockerfile starts differently, with the escape directive. This tells Docker to use the backtick ` option for the escape character, to split commands over multiple lines, rather than the default backslash \ option. With this escape directive I can use backslashes in file paths, and backticks to split long PowerShell commands, which is more natural to Windows users.
The base image is microsoft/iis, which is a Microsoft Windows Server Core image with IIS already set up. I copy an HTML template file from the Docker build context into the root folder. Then I run a PowerShell command to update the content of the template file and save it in the default website location for IIS.
In this Dockerfile, I use three new instructions:
- SHELL specifies the command line to use in RUN commands. The default is cmd, and this switches to powershell.
- ARG specifies a build argument to use in the image with a default value.
- EXPOSE will make a port available in the image, so that containers from the image can have traffic sent in from the host.
This static website has a single home page, which tells you the name of the server that sent the response, with the name of the environment in the page title. The HTML template file has placeholders for the hostname and the environment name. The RUN command executes a PowerShell script to read the file contents, replace the placeholders with the actual hostname and environment value, and then write the contents out.
Containers run in an isolated space, and the host can only send network traffic into the container if the image has explicitly made the port available for use. That's the EXPOSE instruction, which is like a very simple firewall; you use it to expose the ports that your application is listening on. When you run a container from this image, port 80 is available to be published so Docker can serve web traffic from the container.
I can build this image in the usual way, and make use of the ARG command specified in the Dockerfile to override the default value at build time with the --build-arg option:
docker image build --build-arg ENV_NAME=TEST --tag dockeronwindows/ch02-static-website:2e .
Docker processes the new instructions in the same way as those you've already seen: it creates a new, intermediate container from the previous image in the stack, executes the instruction, and extracts a new image layer from the container. After the build, I have a new image which I can run to start the static web server:
> docker container run --detach --publish 8081:80 dockeronwindows/ch02-static-website:2e
6e3df776cb0c644d0a8965eaef86e377f8ebe036e99961a0621dcb7912d96980
This is a detached container so it runs in the background, and the --publish option makes port 80 in the container available to the host. Published ports mean that the traffic coming into the host can be directed into containers by Docker. I've specified that port 8081 on the host should map to port 80 on the container.
You can also let Docker choose a random port on the host, and use the port command to list which ports the container exposes, and where they are published on the host:
> docker container port 6e
80/tcp -> 0.0.0.0:8081
Now I can browse to port 8081 on my machine and see the response from IIS running inside the container, showing me the hostname, which is actually the container ID, and in the title bar is the name of the environment:
The environment name is just a text description, but the value came from the argument is passed to the docker image build command, which overrides the default value from the ARG instruction in the Dockerfile. The hostname should show the container ID, but there's a problem with the current implementation.
On the web page the hostname starts with bf37, but my container ID actually starts with 6e3d. To understand why the ID displayed isn't the actual ID of the running container, I'll look again at the temporary containers used during image builds.