Categories
Software

Improve your local web app development with Docker

A local environment

Create a .env file. It will house environment variables that will allow developers to easily change certain configuration details for the container. This is a file that will be in .gitignore for the project repository – we don’t want to expose potential secret credentials into version control!

.env

HTTP_PORT=8000

This will allow us to use the file in the docker-compose configuration:

docker-compose.yaml

version: '3'

services:
  app:
    working_dir: /app
    stdin_open: true
    image: golang:1.14-alpine
    volumes:
      - ./:/app
    env_file:
      - .env
    ports:
      - ${HTTP_PORT}:8000

As you can see, env_file is configured to point to the local .env file, and we are using the defined HTTP_PORT variable in said file to define the exposed host port (note that it is still pointing to the container’s 8000 port).

Run the up commands again:

$ docker-compose up -d
$ docker-compose exec app go run main.go

And the app should be available on port 8000. Bring the container down, and modify the HTTP_PORT variable to another open port, then visit http://localhost:<HTTP_PORT>. It should now be available there!

Now developers can configure the local ports easily in the container. But what about the initial hard-coding to port 8000? That hard-coding into main.go and docker-compose.yaml is what allowed the application to still be exposed to use, but it forces developers who aren’t using docker to use this port.

While we want to encourage developers to build within the container, we don’t want to unnecessarily hamper them in this way if it can be avoided. With a small improvement we can get the best of both worlds and allow both container-using developers and host-using developers to configure the ports as they see fit.

In the app, use the os package to get the environment variable HTTP_PORT:

main.go

package main

import (
    "net/http"
    "os"
    "log"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello world!"))
    })

    port := os.Getenv("HTTP_PORT")

    log.Printf("listening on port %s", port)

    panic(http.ListenAndServe(":" + port, nil))
}

I have added logging just so you can see that the correct port is being used as well. We then change the docker-compose configuration so that the container port also uses HTTP_PORT:

docker-compose.yaml

version: '3'

services:
  app:
    working_dir: /app
    stdin_open: true
    image: golang:1.14-alpine
    volumes:
      - ./:/app
    env_file:
      - .env
    ports:
      - ${HTTP_PORT}:${HTTP_PORT}

Now if you run the app, be it in the container or on the host, you should be able to use your local .env file to configure the port! If you’re running it directly from the host, source the .env file before running the app:

$ source .env && go run main.go