I'm using docker compose to deploy my app, but I have a need to create a user in one service, and setting its API key on an env file before creating another service.
I'm using a docker-compose.yml with an init service to create user and set env variable with APItoken.
the init service waits for myapp to be healthy, and then, entrypoint.sh creates a user via REST api on myapp and sets is credentials on vars.env.
apiclient waits for init to complete, and gets the user credentials on vars.env set by init.
The problem is: when doing docker compose up -d the apiclient service is created immediately, and only waits init without starting. Therefore, because it is already created, the vars.env is not updated automatically to apiclient.
To make it work, I have to do docker compose up -d again, so that is detects the vars.env file modification and recreates apiclient.
Is is possible to config it so that apiclient waits to be created instead of waiting to start??? or, alternatively, automatically recreated services os execute docker-compose.yaml in two steps?
version: "3.7"
services:
init: # Used to create user on myapp
image: myapp
entrypoint: ["/entrypoint.sh"]
volumes:
- ./config/entrypoint.sh:/entrypoint.sh
- /data/env/vars.env:/vars.env
env_file:
- /data/env/vars.env
depends_on:
myapp:
condition: service_healthy
myapp:
image: myapp
restart: unless-stopped
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "8080"]
env_file:
- /data/env/vars.env
apiclient:
image: apiclient
restart: unless-stopped
depends_on:
init:
condition: service_completed_sucessfully
env_file:
- /data/env/vars.env
You can use health checks to check if "myapp" is running or not. Please refer to the doc for all the details: https://docs.docker.com/compose/compose-file/#healthcheck
Related
I'm new to deployment world and having this issue when I try to deploy an app. The application I tried to deploy is consists of 2 services. First service is an AI model and the second one is the web app. In order to run the web app, the AI model is has to run first. This is the docker-compose.yml that I tried to make:
version: '3.8'
services:
max-image-caption-generator:
image: quay.io/codait/max-image-caption-generator
ports:
- "5000"
app:
build: .
depends_on:
- max-image-caption-generator
ports:
- "8088"
Here are my questions:
Am I defining the docker-compose.yml right?
How do I tell app to run the max-image-caption-generator first?
I was able to build from the file above, I could curl the http://localhost:5000 and it gave me the right html of the AI model, but I couldn't curl http://localhost:8088. It's either connection was reset by peer or it can't connect to the http://localhost:5000 which means the AI model is not running.
Here is couple misunderstandings in your question:
depends_on means that app will be run after max-image-caption-generator, but! Docker will not check if service inside max-image-caption-generator properly started or not. You have to add healthcheck to be sure that max-image-caption-generator is running properly, and after that add condition service_healthy to app.
or it can't connect to the http://localhost:5000
and it can't. Because localhost:5000 only accessible from Docker host but not from container's inside. You have to use container name to be able communicate between containers.
Your docker compose should be like:
version: '3.9'
services:
max-image-caption-generator:
image: quay.io/codait/max-image-caption-generator
ports:
- "5000"
# networks is optional parameter
networks:
service_network:
aliases:
- generator.hostname
# use it if you want to start app after max-image-caption-generator will be ready get requests
# healthcheck:
# test: ["CMD", "some_test_script", "--params"]
# interval: 30s
# timeout: 10s
# retries: 2
app:
build: .
# networks is optional parameter
networks:
- service_network
depends_on:
max-image-caption-generator:
# set this condition if you added healthcheck to max-image-caption-generator container
# condition: service_healthy
# this condition just run app after max-image-caption-generator, and no matter is max-image-caption-generator running properly or not
condition: service_started
ports:
- "8088"
# optional block that may be deleted (docker will use default network)
networks:
service_network:
name: service_network
driver: bridge
ipam:
driver: default
config:
- subnet: 10.0.10.240/28
gateway: 10.0.10.241
After that you will be able to connect to max-image-caption-generator container from app container using http://generator.hostname:5000 url (if networks block is not provided service may be accessed by http://max-image-caption-generator:5000 (same as service key))
*Here you can find information how healthcheck works.
This question already has answers here:
How to stop all containers when one container stops with docker-compose?
(4 answers)
Closed 1 year ago.
I have this docker-compose.yml which runs a node script which depends on Redis.
version: "3.9"
services:
redis:
image: "redis:alpine"
# restart: always
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./docker/redis:/data
node:
image: "node:17-alpine"
user: "node"
depends_on:
- redis
environment:
- NODE_ENV=production
- REDIS_HOST_ENV=redis
volumes:
- ./docker/node/src:/home/node/app
- ./docker/node/log:/home/node/log
expose:
- "8081"
working_dir: /home/node/app
command: "npm start"
When starting this script with docker compose up both services will start. However when the node service is finished, the redis service keeps running. Is there a way to define that the redis service can stop when the node service is done?
I have examined the documentation for Compose Spec but I have not found anything that allows you to immediately stop containers based on the state of another one. Perhaps there really is a way, but you can always control the behaviour of the redis service by using an healthcheck:
services:
redis:
image: "redis:alpine"
# restart: always
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./docker/redis:/data
healthcheck:
test: ping -c 2 mynode || kill 1
interval: 5s
retries: 1
start_period: 20s
node:
image: "node:17-alpine"
container_name: mynode
user: "node"
depends_on:
- redis
environment:
- NODE_ENV=production
- REDIS_HOST_ENV=redis
volumes:
- ./docker/node/src:/home/node/app
- ./docker/node/log:/home/node/log
expose:
- "8081"
working_dir: /home/node/app
command: "npm start"
As for the node service, I have added a container_name: mynode, necessary by the redis service in order to contact it. The container name becomes also the hostname, if not specified with the hostname property.
The redis service has an healthcheck that ping the node container every 5 seconds, starting after 30 seconds from the container start. If the ping is successful, the container is labeled as healthy, otherwise it is killed.
This solution might work in your case but has some downsides:
The healthcheck feature is abused here, besides what if you had another healthcheck?
You cannot always kill the init process, because protected by default. There are some discussions about this and it seems the most popular decision is to use tini as the init process. Fortunately, in the image you are using, it is possible.
redis service contacts the node service via the hostname, which means that they are supposed to be in the same network in your case. The current network is the default bridge network that should be avoided most of the times. I suggest you to declare a custom bridge network.
This solution is based on polling the node container, which is not very elegant, firstly because you have to hope that the time-based parameters in the healthcheck section are "good-enough".
I use docker and also use docker-compose for tie each container.
In my python flask code, refer environment variable like this.
import os
from app import db, create_app
app = create_app(os.getenv('FLASK_CONFIGURATION') or 'development')
if __name__ == '__main__':
print(os.getenv('FLASK_CONFIGURATION'))
app.run(host='0.0.0.0', debug=True)
And docker-compose.yml here.
version: '3.7'
services:
nginx:
build:
context: .
dockerfile: docker/nginx/dockerfile
container_name: nginx
hostname: nginx-prod
ports:
- '80:80'
networks:
- backend
links:
- web_project
depends_on:
- web_project
environment:
- FLASK_CONFIGURATION=production
mongodb:
build:
context: .
dockerfile: docker/mongodb/dockerfile
container_name: mongodb
hostname: mongodb-prod
ports:
- '27017:27017'
networks:
- backend
web_project:
build:
context: .
dockerfile: docker/web-prod/dockerfile
container_name: web_project
hostname: web_project_prod
ports:
- '5000:5000'
networks:
- backend
tty: true
depends_on:
- mongodb
links:
- mongodb
environment:
- FLASK_CONFIGURATION=production
networks:
backend:
driver: 'bridge'
I set FLASK_CONFIGURATION=production via environment command.
But when I execute, maybe FLASK_CONFIGURATION=production doesn't work.
I also tried to ENV FLASK_CONFIGURATION production to each dockerfile. (doesn't work too)
Strange thing is, When I enter to my container via bash(docker exec -it bash) and check the environment variable with export, it was set perfectly.
Is there any wrong code in my docker settings?
Thanks.
[SOLVED]
It is caused by supervisor.
When using supervisor, it's shell is isolated with original.
So we have to define our environment variables into supervisor.conf
Your flask code is looks ok, and as you said ... in bash this ENV variable exists,
My advice to you is to find way to put this variable to .env file in your project.
I will explain why i'm saying it regarding similar issue that i had with cron:
The cron run in his "own world" because the system run and execute it, and because of it he don't share those ENV variables that the bash of the main container process holding.
So i assume (please give feed back if not) that flask run too in similar way in his "own world" and don't have access to those ENV that Docker set.
So, there for, i created bash script that read all ENV variable and write them to the .env file of the project, this script run after the container created.
In this way, no matter from where and how you run the code/script ... those ENV variables will always be exists.
docker-compose 2.1 offers the nice feature to specify a condition with depends_on. The current docker-compose documentation states:
Version 3 no longer supports the condition form of depends_on.
Unfortunately the documentation does not explain, why the condition form was removed and is lacking any specific recommondation on how to implement that behaviour using V3 upwards.
There's been a move away from specifying container dependencies in compose. They're only valid at startup time and don't work when dependent containers are restarted at run time. Instead, each container should include mechanism to retry to reconnect to dependent services when the connection is dropped. Many libraries to connect to databases or REST API services have configurable built-in retries. I'd look into that. It is needed for production code anyway.
From 1.27.0, 2.x and 3.x are merged with COMPOSE_SPEC schema.
version is now optional. So, you can just remove it and specify a condition as before:
services:
web:
build: .
depends_on:
redis:
condition: service_healthy
redis:
image: redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30
There are some external tools that let you mimic this behaviour. For example, with the dockerize tool you can wrap your CMD or ENTRYPOINT with dockerize -wait and that will prevent running your application until specified services are ready.
If your docker-compose file used to look like this:
version: '2.1'
services:
kafka:
image: spotify/kafka
healthcheck:
test: nc -z localhost 9092
webapp:
image: foo/bar # your image
healthcheck:
test: curl -f http://localhost:8080
tests:
image: bar/foo # your image
command: YOUR_TEST_COMMAND
depends_on:
kafka:
condition: service_healthy
webapp:
condition: service_healthy
then you can use dockerize in your v3 compose file like this:
version: '3.0'
services:
kafka:
image: spotify/kafka
webapp:
image: foo/bar # your image
tests:
image: bar/foo # your image
command: dockerize -wait tcp://kafka:9092 -wait web://webapp:8080 YOUR_TEST_COMMAND
Just thought I'd add my solution for when running postgres and an application via docker-compose where I need the application to wait for the init sql script to complete before starting.
dockerize seems to wait for the db port to be available (port 5432) which is the equivilant of depends_on which can be used in docker 3:
version: '3'
services:
app:
container_name: back-end
depends_on:
- postgres
postgres:
image: postgres:10-alpine
container_name: postgres
ports:
- "5432:5432"
volumes:
- ./docker-init:/docker-entrypoint-initdb.d/
The Problem:
If you have a large init script the app will start before that completes as the depends_on only waits for the db port.
Although I do agree that the solution should be implemented in the application logic, the problem we have is only for when we want to run tests and prepopulate the database with test data so it made more sense to implement a solution outside the code as I tend not like introducing code "to make tests work"
The Solution:
Implement a healthcheck on the postgres container.
For me that meant checking the command of pid 1 is postgres as it will be running a different command on pid 1 while the init db scripts are running
Write a script on the application side which will wait for postgres to become healthy. The script looks like this:
#!/bin/bash
function check {
STATUS=\`curl -s --unix-socket /var/run/docker.sock http:/v1.24/containers/postgres/json | python -c 'import sys, json; print json.load('sys.stdin')["State"]["Health"]["Status"]'\`
if [ "$STATUS" = "healthy" ]; then
return 0
fi
return 1
}
until check; do
echo "Waiting for postgres to be ready"
sleep 5
done
echo "Postgres ready"
Then the docker-compose should mount the directories of the scripts so that we don't edit the Dockerfile for the application and if we're using a custom postgres image, this way we can continue to use the docker files for your published images.
We're also overriding the entry point defined in the docker file of the app so that we can run the wait script before the app starts
version: '3'
services:
app:
container_name: back-end
entrypoint: ["/bin/sh","-c","/opt/app/wait/wait-for-postgres.sh && <YOUR_APP_START_SCRIPT>"]
depends_on:
- postgres
volumes:
- //var/run/docker.sock:/var/run/docker.sock
- ./docker-scripts/wait-for-postgres:/opt/app/wait
postgres:
image: postgres:10-alpine
container_name: postgres
ports:
- "5432:5432"
volumes:
- ./docker-init:/docker-entrypoint-initdb.d/
- ./docker-scripts/postgres-healthcheck:/var/lib
healthcheck:
test: /var/lib/healthcheck.sh
interval: 5s
timeout: 5s
retries: 10
I reached this page because one container would not wait for the one depending upon and I had to run a docker system prune to get it working. There was an orphaned container error that prompted me to run the prune.
I have a docker compose file that defines a service that will run my application and a service that that application is dependent on to run:
services:
frontend:
build:
context: .
volumes:
- "../.:/opt/app"
ports:
- "8080:8080"
links:
- redis
image: node
command: ['yarn', 'start']
redis:
image: redis
expose:
- "6379"
For development this compose file exposes 8080 so that I can access the running code from a browser.
In jenkins however I can't expose that port as then two jobs running simultaneously would conflict trying to bind to the same port on jenkins.
Is there a way to prevent docker-compose from binding service ports? Like an inverse of the --service-ports flag?
For context:
In jenkins I run tests using docker-compose run frontend yarn test which won't map ports and so isn't a problem.
The issue presents when I try to run end to end browser tests against the application. I use a container to run CodeceptJS tests against a running instance of the app. In that case I need the frontend to start before I run the tests, as they will fail if the app is not up.
Q. Is there a way to prevent docker-compose from binding service ports?
It has no sense to prevent something that you are asking to do. docker-compose will start stuff as the docker-compose.yml file indicates.
I propose duplicate the frontend service using extends::
version: "2"
services:
frontend-base:
build:
context: .
volumes:
- "../.:/opt/app"
image: node
command: ['yarn', 'start']
frontend:
extends: frontend-base
links:
- redis
ports:
- "8080:8080"
frontend-test:
extends: frontend-base
links:
- redis
command: ['yarn', 'test']
redis:
image: redis
expose:
- "6379"
So use it as this:
docker-compose run frontend # in dev environment
docker-compose run frontend-test # in jenkins
Note that extends: is not available in version: "3", but they will bring it back again in the future.
For preventing to publish ports outside the docker network you just
need to write on a single port in the ports segment.
Instead of using this:
ports:
- 8080:8080
Just use this one(at below):
ports:
- 8080