This is a crosspost from adventofdocker.com
Yesterday we learned that containers are ephemeral - any changes made inside a container are lost when it stops. But what if you need to persist data? That's where volumes come in!
Let's modify our HTTP server to save some data:
package main import ( "fmt" "net/http" "os" "time" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Record new visit timestamp := time.Now().Format(time.RFC3339) + "\n" os.MkdirAll("data", 0755) f, _ := os.OpenFile("data/visits.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) f.WriteString(timestamp) f.Close() // Read and display all visits data, err := os.ReadFile("data/visits.txt") if err != nil { fmt.Fprintf(w, "Error reading visits!") return } fmt.Fprintf(w, "New visit recorded at %s\n\nAll visits:\n%s", timestamp, string(data)) }) fmt.Println("Listening on port 8080") http.ListenAndServe(":8080", nil) }
Our Dockerfile stays the same:
FROM golang COPY . . RUN go build -o main main.go EXPOSE 8080 CMD ["./main"]
Let's build and run it:
$ docker build -t hello-world-go . $ docker run -p 8080:8080 hello-world-go
When you visit localhost:8080, each visit gets recorded and displayed. However, if you stop and restart the container, all your visits are gone! This is because containers are ephemeral - any changes made inside them are lost when they stop.
Solving with Docker Volumes
We can fix this by using volumes. First, create a named volume:
$ docker volume create mydata
Now run the container with the volume mounted:
$ docker run -p 8080:8080 -v mydata:/data hello-world-go
The -v mydata:/data
flag mounts our volume named mydata
to the /data
directory inside the container. Try it out:
- Visit localhost:8080 a few times - you'll see the list grow with each visit
- Stop the container with
docker stop <container_id>
- Start a new container with the same volume:
$ docker run -p 8080:8080 -v mydata:/data hello-world-go
Your visits are still there! The data persists even when containers are stopped or removed.
Managing Volumes
List all volumes:
$ docker volume ls DRIVER VOLUME NAME local mydata
Inspect a volume:
$ docker volume inspect mydata [ { "CreatedAt": "2024-12-07T...", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/mydata/_data", "Name": "mydata", "Options": {}, "Scope": "local" } ]
Remove a volume:
$ docker volume rm mydata
You can also use anonymous volumes by omitting the volume name:
$ docker run -v /data hello-world-go
Docker will create a random volume name for you. These are harder to manage but useful for temporary data.
Bind Mounts
Instead of using a Docker-managed volume, you can mount a directory from your host directly:
$ docker run -v $(pwd)/data:/data hello-world-go
This mounts the ./data
directory from your current location into the container. Great for development, I recommend reading more about bind mounts here.
That's it for today! Tomorrow we'll have our first quiz to test what you've learned this week. Make sure you're comfortable with:
- Basic Docker commands
- Building images
- Running containers
- Port mapping
- Layers
- Volumes
Until then, happy coding! 🐳🎄
Jonas
Top comments (1)
Hey man! Nice tutorial. Just a suggestion, don't use relative path "data/visits.txt", instead use "/data/visits. I dont know why this is happening. my default workdir is /go but data directory is created in root (/), and yet the volume is not able to read it like it is inside that go directory.
I actually struggled for an hour on this. I made the path absolute and it worked.
To test this i made my WORKDIR / and my RUN go build -o main go/main.go when the paths are relative in main.go and it worked.