.env
file to store configuration with docker-compose swarm stacks and services.Since a few years, a lot of projects based on docker, even in the open source community, comes with a .env
file to store configuration.
It allows to define specific configuration for deployments (development, staging and production for instance).
It’s a good starting point if your try to respect the twelve-factor app methodology,
but you should NOT use a single .env
file with docker swarm stack and services or you will have some organisation, security and performance problems.
Let’s take a PHP Symfony project developed and deployed thanks to docker and docker-compose on a swarm cluster.
In that kind of configuration we will often work with a single .env
file used by every part of our technical stack:
Let’s take these typical docker-compose.yml
and .env
files examples:
REGISTRY_PATH=registry.domain.tld | |
TAG_PHP=7.2 | |
TAG_NGINX=1.17.5 | |
HTTP_PORT=2000 | |
REPLICA_COUNT_PHP=3 | |
REPLICA_COUNT_NGINX=3 | |
DATABASE_NETWORK=mysql-005 | |
NGINX_MEM_LIMIT=215M | |
PHP_MEM_LIMIT=2G | |
MEMORY_LIMIT=2G | |
HOSTNAME=domain.tld | |
DB_HOST=127.0.0.1 | |
DB_PORT=3306 | |
DB_NAME=test | |
DB_USER=root | |
DB_PASSWORD=very_sensitive_password |
version: '3.5' | |
services: | |
# Php service configuration | |
php: | |
image: ${REGISTRY_PATH}/php:${TAG_PHP} | |
env_file: .env | |
networks: | |
- default | |
- database | |
deploy: | |
replicas: ${REPLICA_COUNT_PHP:-1} | |
resources: | |
limits: | |
memory: "${PHP_MEM_LIMIT:-1G}" | |
# Nginx service configuration | |
nginx: | |
image: ${REGISTRY_PATH}/nginx:${TAG_NGINX} | |
env_file: .env | |
networks: | |
- default | |
ports: | |
- ${HTTP_PORT}:80 | |
deploy: | |
replicas: ${REPLICA_COUNT_NGINX:-1} | |
resources: | |
limits: | |
memory: "${NGINX_MEM_LIMIT:-1G}" | |
networks: | |
default: | |
# The database network is external because used by many docker stacks | |
database: | |
external: true | |
name: "${DATABASE_NETWORK}" |
.env
file ?Let’s say you use a project like this perfect one: jwilder/nginx-proxy.
VIRTUAL_HOST=domain.localhost
to your .env
file to access Nginx web server with your custom domain “domain.localhost”.nginx.1 | 2019/10/07 07:32:39 [error] 147#147: *105 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 172.17.0.1, server: domain.localhost, request: "POST / HTTP/1.1", upstream: "http://172.17.0.10:9000", host: "domain.localhost", referrer: "http://domain.localhost"
nginx.1 | 2019/10/07 07:32:39 [warn] 147#147: *105 upstream server temporarily disabled while reading response header from upstream, client: 172.17.0.1, server: domain.localhost, request: "POST / HTTP/1.1", upstream: "http://172.17.0.10:9000", host: "domain.localhost", referrer: "http://domain.localhost"
This is because adding a variable to the .env
file, will add it into all containers (configured with env_file
directive).
So with the round robin algorithm used by default within Nginx load balancing, the request will reach the PHP container one time out of two, instead of the Nginx container.
As we said in the previous part, “adding a variable to the .env
file, will add it into all containers (configured with env_file
directive)”.
So you will have access all environment variables in all running containers… In addition to the fact that it is unnecessary, it can introduce vulnerabilities.
Imagine your Nginx web server is compromised and some hackers can export all env variables available in your Nginx running container, they will have access to sensitive information like database credentials:
They will also have access to other sensitive information:
REGISTRY_PATH
(registry.domain.tld)Docker has a built-in mecanism allowing to restart containers when dependencies have changed: env variables, docker-compose directives, networks,…
I’m sure you see where I’m going with this… Imagine you want to increase PHP memory limit, you will change the value of env variable MEMORY_LIMIT
.
Then you will run that kind of command to update your PHP service:
docker stack deploy -c docker-compose.yml --with-registry-auth "symfony_application"
And BOOM !!! All the containers (probably spread over many servers, maybe over many datacenters)
of all your services of your stack will restart following defined restart_policy
, update_config
and healthcheck
configurations.
This will lead to an extra and unnecessary resources consumption (CPU, memory, bandwith, …) and maybe service downtime. Our example is pretty simple but it’s a common thing to have 5 or 6 services per stack, for instance:
We can’t afford to restart everything when we simply want to update a single service.
Choose the one you prefer or the one that best fits your needs.
env_file
(or --env-file
) configuration.Docker-compose allows us to define environment variables to pass to running containers, with environment
config, this way no other variable will be available in the container:
version: '3.5' | |
services: | |
# Php service configuration | |
php: | |
image: ${REGISTRY_PATH}/php:${TAG_PHP} | |
networks: | |
- default | |
- database | |
environment: | |
MEMORY_LIMIT: "${MEMORY_LIMIT}" | |
DB_HOST: "${DB_HOST}" | |
DB_PORT: "${DB_PORT}" | |
DB_NAME: "${DB_NAME}" | |
DB_USER: "${DB_USER}" | |
DB_PASSWORD: "${DB_PASSWORD}" | |
deploy: | |
replicas: ${REPLICA_COUNT_PHP:-1} | |
resources: | |
limits: | |
memory: "${PHP_MEM_LIMIT:-1G}" | |
# Nginx service configuration | |
nginx: | |
image: ${REGISTRY_PATH}/nginx:${TAG_NGINX} | |
environment: | |
HOSTNAME: "${HOSTNAME}" | |
networks: | |
- default | |
ports: | |
- ${HTTP_PORT}:80 | |
deploy: | |
replicas: ${REPLICA_COUNT_NGINX:-1} | |
resources: | |
limits: | |
memory: "${NGINX_MEM_LIMIT:-1G}" | |
networks: | |
default: | |
# The database network is external because used by many docker stacks | |
database: | |
external: true | |
name: "${DATABASE_NETWORK}" |
and the matching docker-compose.yml
:
REGISTRY_PATH=registry.domain.tld | |
TAG_PHP=7.2 | |
TAG_NGINX=1.17.5 | |
HTTP_PORT=2000 | |
REPLICA_COUNT_PHP=3 | |
REPLICA_COUNT_NGINX=3 | |
DATABASE_NETWORK=mysql-005 | |
NGINX_MEM_LIMIT=215M | |
PHP_MEM_LIMIT=2G |
MEMORY_LIMIT=2G | |
DB_HOST=127.0.0.1 | |
DB_PORT=3306 | |
DB_NAME=test | |
DB_USER=root | |
DB_PASSWORD=very_sensitive_password |
version: '3.5' | |
services: | |
# Php service configuration | |
php: | |
image: ${REGISTRY_PATH}/php:${TAG_PHP} | |
env_file: .php.env | |
networks: | |
- default | |
- database | |
deploy: | |
replicas: ${REPLICA_COUNT_PHP:-1} | |
resources: | |
limits: | |
memory: "${PHP_MEM_LIMIT:-1G}" | |
# Nginx service configuration | |
nginx: | |
image: ${REGISTRY_PATH}/nginx:${TAG_NGINX} | |
env_file: .nginx.env | |
networks: | |
- default | |
ports: | |
- ${HTTP_PORT}:80 | |
deploy: | |
replicas: ${REPLICA_COUNT_NGINX:-1} | |
resources: | |
limits: | |
memory: "${NGINX_MEM_LIMIT:-1G}" | |
networks: | |
default: | |
# The database network is external because used by many docker stacks | |
database: | |
external: true | |
name: "${DATABASE_NETWORK}" |
When building your docker image you may add all your env files in the image if you are not careful.
Just see .dockerignore
file or RUN rm -f *.env
或是邮件反馈可也:
askdama[AT]googlegroups.com
订阅 substack 体验古早写作:
关注公众号, 持续获得相关各种嗯哼: