Steps

What's inside a container image?

Step1 of 6

Write a simple web server

Start by writing a simple Go web server application. This is very similar to the app in the Container basics scenario, but this time the response content is read from a file rather than being hard-coded.

Hello world

Write a basic web server in Go that will respond to a request on port 8080 with a simple message like "hello world". The editor pane on the top right of this screen is already set up to edit a file called hello.go. You can click the button below to copy the code into that file.

You'll need to define the response file that this application code reads from. First let's open the file in the editor:

text/response

And copy the message into that text file. (Feel free to change it to make it your own!)

Here's a response

Run your app

Run the app in the background (using the ampersand) so that you can still type into the terminal.

./hello &

Check you get the response you expect when making a request to your application using curl:

curl localhost:8080

You should see

the output from the call to fmt.Printf which displays the contents of the response file, and

the text message returned to your request.

It should look something like this:

$ curl localhost:8080
response: Here's a response
Here's a response

Stop the application

When you're satisfied that this application works, let's stop it:

kill %1

Next step

At this stage you have a compiled Go binary and a text file that it reads from. In the next step we'll build a container image that includes both the binary and the text file.

Put your app into a container image

In this step you'll build a container image that encapsulates your app along with the text file it needs in order to run.

Write a Dockerfile

The Dockerfile describes the steps that Docker will take to build your container image.

# Start with the scratch (empty) image
FROM scratch
# Copy the hello binary into the root directory
COPY hello /
# Copy the response text file into the location where the container expects it to be
COPY text/response /text/response
# Tell Docker what executable to run by default when starting this container
ENTRYPOINT ["/hello"]

Build a container image from this Dockerfile

The next command builds a container image, following the commands in the Dockerfile.

docker build -t hello .

You have built a container image that includes both the executable and the text file it needs in order to run. This is one of the big advantages of containers - a container image can act as a package for everything that an application needs in order to run. If you pass the container image to someone else, it has everything they need and the container should run just the same on their system as it does on yours.

Now let's move on to running this container.

Run your container image

Now it's time to run your app inside a container.

docker run --rm -d -p 18080:8080 hello

Check you can access it

Note that we have mapped port 8080 inside the container to port 18080 on the host, so you can send a request to this container on port 18080.

We copied the text/response file so that it's part of the container image at the location /text/response. This container image is self-contained - wherever you run it, the file exists at /text/response from the container's perspective. Note that this isn't/text/response on the host where you're running the container. The filesystem in the container is different from what the host sees.

You can verify this is true by using the editor panel above to change the contents of the text/response file.

text/response

curl localhost:18080

Even though the application reads the file every time you make a request, changing the file here doesn't have any effect. This is because the application is reading the file from the container's own version of the filesystem.

Later, we'll see how you can mount a volume into a container so that it can see files on the host filesystem. But first, let's find out what happened to that fmt.Printf() message.

Get logs from a container

The answer is that by default, output from stdout and stderr are sent to Docker logs. To see these logs we can simply run the command docker ps <container ID>. But we'll need to get hold of the container ID first.

Get container ID

To retrieve the logs we'll need the identity of the running container:

container ID - in this example it's 63e325... (recall that you only need to use enough of the first few hex digits to unqiuely identify the container)

name - in the example it's gifted_ardinghelli

The container ID and name you'll see will be different.

Here's a shortcut to getting the container ID for the most recently run container:

docker ps -ql

The -q flag returns just the numeric ID

The -l flag returns the most recently run container

The -q flag is neat, because you can use the ID as input to other docker container commands, like docker logs.

Get the logs

The following command gets the logs from the most recently run container:

docker logs $(docker ps -ql)

When you run this you should see any output that has been sent to stdout or stderr.

Optional extra task

Want to confirm that stderr also gets written to Docker logs? You could do easily do this by modifying your app.

Edit hello.go to add a line that prints something to stderr. You can do this with fmt.Fprintf(os.Stderr, "your text here\n"). You decide where it goes, and what it's going to say.

hello.go

Recompile hello.go:

CGO_ENABLED=0 go build -o hello hello.go

Rebuild the container to include your changed app:

docker build -t hello .

Stop the currently running instance of the container:

docker stop $(docker ps -ql)

Run an instance of the new version of the container:

docker run --rm -d -p 18080:8080 hello

Check it's working:

curl localhost:18080

Get the logs:

docker logs $(docker ps -ql)

Did you see the output for both stderr and stdout in the logs?

You might think that there are a lot of manual steps between editing your Go code and running it. We'll see some ways to automate some of these intermediate steps in another scenario.

Next step

Remember that your app is reading its response from a text file that has been built into the container image? In the next step we'll see how you can mount directories into a container so that the container can access files that are stored on the host.

Mounting a directory from the host

In this step we're going to create a different response file, and we're going to make it accessible to the container so that it can read the file directly from the host, using a mount.

Create a new response file

mkdir different

touch different/response

Click the blue link below to open this new file that you have just created:

different/response

Copy the contents into that response file.

This is a different response

Note that this file exists at different/response, but the application code still expects to read the file from text/response.

Mount the directory

Run the container, but this time use the -v parameter to mount the different directory on the host so that it appears to the container at the location text.

docker run -d --rm -v "$(pwd)/different":/text -p 18081:8080 hello

Check the response:

curl localhost:18081

Change the response

Try editing the contents of different/response (which is a file on your host) you should see a different response from the web server in the container, without having to restart the container.

And this is another response altogether

curl localhost:18081

The container is able to access that file because that directory is mounted in.

Also note that the container image still has the text file built into it at the location text/response but this is overwritten by the mount.

Notes and further reading

If you have permission to run Docker commands, there is nothing stopping you from mounting any directory from the host to make it accessible from the container. This can be a security risk.

Mounting a sensitive directory like /etc or even the root directory into a container can allow a user to escalate their privileges. See an example in this video (This demo uses Kubernetes, but the directory mounting mechanism works in just the same way you have used above).

Next step

In the next step you'll see how to pass environment variables into your application.

Passing environment variables into a container

In this step you'll pass a message to your application as an environment variable.

Modify the Go program to read an environment variable and use that for the web response:

Opening...

Opening...

Opening...

Help

Katacoda offerings an Interactive Learning Environment for Developers. This course uses a command line and a pre-configured sandboxed environment for you to use. Below are useful commands when working with the environment.

cd <directory>

Change directory

ls

List directory

echo 'contents' > <file>

Write contents to a file

cat <file>

Output contents of file

Vim

In the case of certain exercises you will be required to edit files or text. The best approach is with Vim. Vim has two different modes, one for entering commands (Command Mode) and the other for entering text (Insert Mode). You need to switch between these two modes based on what you want to do. The basic commands are: