├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── main.go └── test ├── Dockerfile ├── main.go └── web_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.5.1-onbuild 2 | MAINTAINER Matt Fellows 3 | 4 | ENV PORT 80 5 | EXPOSE 80 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matt Fellows 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dockerize Demo - Wait for containers 2 | 3 | Simple Docker Compose setup: front-end [API](https://github.com/mefellows/dockerize-compose-demo/blob/master/main.go) that takes 5 seconds to start up, and a [test](https://github.com/mefellows/dockerize-compose-demo/blob/master/test/web_test.go) harness that hits the API 100 times, failing if it receives any non-20x response codes. 4 | 5 | ## Running 6 | 7 | ### Failing Example 8 | 9 | ``` 10 | git co fail 11 | docker-compose up 12 | ``` 13 | 14 | The test should fail with something like: 15 | 16 | > test_1 | web_test.go:46: Error not expected: Get http://api/: dial tcp 172.17.0.3:80: getsockopt: connection refused 17 | 18 | This is because Compose knows the container has started, but not the service within it. 19 | 20 | ### Passing Example 21 | 22 | Using [Dockerize](https://github.com/jwilder/dockerize), we can ensure the tests don't run until the dependent API is up and running: 23 | 24 | ``` 25 | git co dockerize 26 | docker-compose up 27 | ``` 28 | 29 | ## Background 30 | 31 | It is common when using tools like [Docker Compose](https://docs.docker.com/compose/) to depend on services in other linked containers, however oftentimes relying on [links](https://docs.docker.com/compose/compose-file/#links) is not enough - whilst the container itself may have _started_, the _service(s)_ within it may not yet be ready - resulting in shell script hacks to work around race conditions. 32 | 33 | This [PR](https://github.com/jwilder/dockerize/pull/23/) gives `dockerize` the ability to wait for services on a specified protocol (`tcp`, `tcp4`, `tcp6`, `http`, and `https`) before starting the main application: 34 | 35 | ``` 36 | dockerize -wait tcp://web:80 -wait http://web:80 37 | ``` 38 | 39 | I've found this to be particularly useful when using Docker Compose as a test harness, where one of the containers needs to test another. Instead of `netcat`ing my way around the problem, I can just wrap the command using `dockerize`. 40 | 41 | 42 | 43 | ## References / Related reading 44 | 45 | * https://github.com/docker/compose/issues/374#issuecomment-126312313 46 | * https://github.com/docker/compose/issues/235 47 | * https://github.com/jwilder/dockerize 48 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | api: 2 | build: . 3 | ports: 4 | - "8000:80" 5 | expose: 6 | - "80" 7 | 8 | test: 9 | build: test 10 | command: dockerize -wait http://api:80 go test -v ./... 11 | links: 12 | - api:api 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "time" 9 | ) 10 | 11 | func main() { 12 | time.Sleep(5 * time.Second) 13 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 14 | fmt.Fprintf(w, "Hello from API") 15 | }) 16 | log.Fatal(http.ListenAndServe(":80", nil)) 17 | } 18 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.5.1 2 | RUN mkdir /app 3 | COPY . /app 4 | WORKDIR /app 5 | 6 | RUN apt-get update && apt-get install -y wget 7 | RUN wget https://github.com/jwilder/dockerize/releases/download/v0.1.0/dockerize-linux-amd64-v0.1.0.tar.gz 8 | RUN tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.1.0.tar.gz 9 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package examples 2 | -------------------------------------------------------------------------------- /test/web_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "testing" 10 | ) 11 | 12 | func Test_Example100calls(t *testing.T) { 13 | fmt.Println("Running tests") 14 | 15 | host := fmt.Sprintf("http://api/") 16 | wait := &sync.WaitGroup{} 17 | const NR_REQUESTS = 100 18 | 19 | wait.Add(NR_REQUESTS) 20 | for i := 0; i < NR_REQUESTS; i++ { 21 | go func() { 22 | defer wait.Done() 23 | resp, err := http.Get(host) 24 | checkErr(err, false, t) 25 | fmt.Println(resp) 26 | 27 | if resp != nil { 28 | // fmt.Println("\nResponse:") 29 | r := bufio.NewReader(resp.Body) 30 | r.WriteTo(os.Stdout) 31 | // fmt.Println() 32 | } else { 33 | fmt.Println("No response body") 34 | } 35 | if resp.StatusCode != 200 { 36 | t.Fatalf("Expected 200 response code, but got %d", resp.StatusCode) 37 | } 38 | }() 39 | } 40 | fmt.Println("Waiting for all requests to finish...") 41 | wait.Wait() 42 | } 43 | 44 | func checkErr(err error, expected bool, t *testing.T) { 45 | if err != nil && !expected { 46 | t.Fatalf("Error not expected: %s", err.Error()) 47 | 48 | } else if err == nil && expected { 49 | t.Fatalf("Error expected, but did not get one") 50 | } 51 | } 52 | --------------------------------------------------------------------------------