Hey there! Congrats to making it to day 5. Today we will continue with our Go application from Day 4 and make it a bit more interesting. We will start by running a simple HTTP server and then see how we can interact with it!
As a quick reminder, yesterday we created a Dockerfile that looked like this:
FROM golang
COPY . .
RUN go build -o main main.go
CMD ["./main"]
And our main.go file looked like this:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
If you didnโt create it yesterday, please create a new directory and add the files to it!
Printing hello world is of course a bit boring, so letโs make our application a bit more interesting by adding a simple HTTP server.
To do this we will modify our main.go file to look like this:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World!") // this will be printed as a response when you request /
})
fmt.Println("Listening on port 8080")
http.ListenAndServe(":8080", nil) // this will start the HTTP server (blocking operation) and listen on port 8080
}
Here we basically just create one route (/
) that will print โHello World!โ when someone visits the root path. The HTTP server will be listening on port 8080. If you feel adventurous, you can try to add some more routes, change the port or the message.
You can still use the same Dockerfile from yesterday and build the image just like we did yesterday.
$ docker build -t hello-world-go .
And then run it just like we did yesterday.
$ docker run hello-world-go
If you now open your browser and navigate to http://localhost:8080
you would expect to see โHello, World!โ printed in your browser, right?
Well, not reallyโฆ
This is because Docker containers are isolated from the host by default. This means that if you listen on a port in a container, it wonโt be accessible from the host. You need to explicitly tell Docker that you want to expose a port. To do this, kill your running container (CTRL+C in your terminal) and modify your run command:
$ docker run -p 8080:8080 hello-world-go
This will expose port 8080 of the container to port 8080 on the host. Now if you navigate to http://localhost:8080
you should see โHello, World!โ printed in your browser.
Awesome! I think this is a great moment to play around with the ports a bit. Change the ports in your main.go
file and see how it behaves.
Side note: If you see Dockerfiles that other people wrote, you will often see an EXPOSE
instruction. The expose instruction actually doesnโt expose the port to the host, it is basically just documentation to bridge the knowledge between the developer who wrote the Dockerfile and the people who will use it. You can read more about it here.
FROM golang
COPY . .
RUN go build -o main main.go
EXPOSE 8080
CMD ["./main"]
Interacting with running containers
I want to show you three helpful commands to interact with your containers. docker ps
, docker stats
, and docker exec
!
During development it is not unusual that you have a bunch of containers running at the same time. You might have one for your API, one for your Frontend and one for your Database. What if you want to know what is currently running?
That is what docker ps
is for. docker ps
lists the currently running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
27a27b6343a7 http-server "./main" 6 minutes ago Up 6 minutes 0.0.0.0:8080->8080/tcp funny_galois
Here you can see that we have one container running called funny_galois
that is running the http-server
image and listening on port 8080. Pretty useful, right?
If the output is empty, itโs probably because your container has already stopped. You can list all containers that have ever been run with the docker ps -a
command.
Sometimes you want to know more about the resource usage of your containers. For this docker stats
is your friend. docker stats
will show you the resource usage of your containers:
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
27a27b6343a7 funny_galois 0.00% 4.914MiB / 7.661GiB 0.06% 4.59kB / 846B 0B / 0B 6
Here you can see that our funny_galois
container has used 0.00% of the CPU and 4.914MiB of memory out of the limit of 7.661GiB (which is 0.06% of the memory). It has done 4.59kB of network I/O and 0B of block I/O and has 6 PIDs running. Super useful for debugging!
Now finally, I want to show you how to interact with a running container. What if you want to poke around in the container? Open a shell? Install some packages? Generally this isnโt recommended (because containers should be ephemeral and stateless) but it can be useful for debugging. To do this we can use the docker exec
command.
$ docker exec -it funny_galois bash
funny_galois
is the name of our container, you can get the name of your container with docker ps
from above. The -it
flags are used to attach your terminal to the container so you can use it. The bash
command will open a shell in the container. If you run this command you will see that you are dropped into the container, from here you can navigate around, install packages, poke around etc. For example, you might want to just check what is in your current directory with ls
:
docker exec -ti funny_galois bash
root@27a27b6343a7:/go# ls
Dockerfile bin main main.go src
I recommend you to try this out and do some exploring in the container. Add files, install some packages. Have fun! Then stop your container and do it again, are the files still there? Why not?
Thatโs the teaser for tomorrow, where we will take a step back and look at what exactly layers are, why containers are ephemeral and what that means for our workflow :)
Until then, happy hacking! ๐
Jonas