ypereirareis.github.io /blog/2019/10/28/why-you-should-split-env-file-with-docker-compose-docker-swarm-stack-and-services/

Why you should split your env file with docker-compose and docker swarm stack and services

Yannick Pereira-Reis 14-18 minutes

PHP-FPM

TLDR: questions answered in this article.

Using a .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}"

What are the problems in the case of single .env file ?

Problem #1: Code organization and separation of concerns.

Problem #2: Bugs.

Let’s say you use a project like this perfect one: jwilder/nginx-proxy.

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.

Problem #3: Security.

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:

Problem #4: Stack update, performance and resources consumption.

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.

Possibles solutions.

Choose the one you prefer or the one that best fits your needs.

Solution #1: Never use the 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}"

Solution #2: Split your env file into multiple env files.

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}"

Another thing to consider.

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


::...
免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


订阅 substack 体验古早写作:


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::