I have built a containerised python application which runs without issue locally using a .env file and and a docker-compose.yml file compiled with compose build.
I am then able to use variables within the Dockerfile like this.
ARG APP_USR
ENV APP_USR ${APP_USR}
ARG APP_PASS
ENV APP_PASS ${APP__PASS}
RUN pip install https://${APP_USR}:${APP_PASS}#github.org/*****/master.zip
I am deploying to cloud run via a synced bitbucket repository, and have defined under "REVISIONS" > "SECRETS AND VARIABLES",(as described here: https://cloud.google.com/run/docs/configuring/environment-variables)
but I can not work out how to access these variables in the Dockerfile during build.
As I understand it, I need to create a cloudbuild.yaml file to define the variables, but I haven't been able to find a clear example of how to set this up using the Environment variables defined in cloud run.
My understanding is that it is not possible to directly use a Cloud Run revision's environment variables in the Dockerfile because the build is managed by Cloud Build, which doesn't know about Cloud Run revision before the deployment.
But I was able to use Secret Manager's secrets in the Dockerfile.
Sources:
Passing secrets from Secret Manager to cloudbuild.yaml: https://cloud.google.com/build/docs/securing-builds/use-secrets
Passing an environment variable from cloudbuild.yaml to Dockerfile: https://vsupalov.com/docker-build-pass-environment-variables/
Quick summary:
In your case, for APP_USR and APP_PASS:
Grant the Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) IAM role for the secret to the Cloud Build service account (see first source).
Add an availableSecrets block at the end of the cloudbuild.yaml file (out of the steps block):
availableSecrets:
secretManager:
- versionName: <APP_USR_SECRET_RESOURCE_ID_WITH_VERSION>
env: 'APP_USR'
- versionName: <APP_PASS_SECRET_RESOURCE_ID_WITH_VERSION>
env: 'APP_PASS'
Pass the secrets to your build step (depends on how you summon docker build, Google's documentation uses 'bash', I use Docker directly):
- id: Build
name: gcr.io/cloud-builders/docker
args:
- build
- '-f=Dockerfile'
- '.'
# Add these two `--build-arg` params:
- '--build-arg'
- 'APP_USR=$$APP_USR'
- '--build-arg'
- 'APP_PASS=$$APP_PASS'
secretEnv: ['APP_USR', 'APP_PASS'] # <=== add this line
Use these secrets as standard environment variables in your Dockerfile:
ARG APP_USR
ENV APP_USR $APP_USR
ARG APP_PASS
ENV APP_PASS $APP_PASS
RUN pip install https://$APP_USR:$APP_PASS#github.org/*****/master.zip
You have several way to achieve that.
You can, indeed, create your container with your .env in it. But it's not a good practice because your .env can contain secret (API Key, database password,...) and because your container is tight to an environment
The other solution is to deploy your container on Cloud Run (not a docker compose, it doesn't work on Cloud Run), and add the environment variable with the revision. use, for example, --set-env-vars=KEY1=Value1 format to achieve that.
If you have secrets, you can store them in secret manager and load it as env var at runtime, or as volume
The last solution, if you can specify where your container will get the .env file in your file tree (I'm not expert in Python to help you on that), you can use this trick that I described in this article. It's perfectly designed for configuration file, it's stored natively in Secret manager and therefore protect your secret automatically.
Related
I have a Flask web application running as a Docker image that is deployed to a Kubernetes pod running on GKE. There are a few environment variables necessary for the application which are included in the docker-compose.yaml like so:
...
services:
my-app:
build:
...
environment:
VAR_1: foo
VAR_2: bar
...
I want to keep these environment variables in the docker-compose.yaml so I can run the application locally if necessary. However, when I go to deploy this using a Kubernetes deployment, these variables are missing from the pod and it throws an error. The only way I have found to resolve this is to add the following to my deployment.yaml:
containers:
- name: my-app
...
env:
- name: VAR_1
value: foo
- name: VAR_2
value: bar
...
Is there a way to migrate the values of these environment variables directly from the Docker container image into the Kubernetes pod?
I have tried researching this in Kubernetes and Docker documentation and Google searching and the only solutions I can find say to just include the environment variables in the deployment.yaml, but I'd like to retain them in the docker-compose.yaml for the purposes of running the container locally. I couldn't find anything that explained how Docker container environment variables and Kubernetes environment variables interacted.
Kompose can translate docker compose files to kubernetes resources:
https://kubernetes.io/docs/tasks/configure-pod-container/translate-compose-kubernetes/
Let us assume docker-compose file and kubernetes runs the same way,
Both take a ready to use image and schedule a new pod or container based on it.
By default this image accept a set of env variables, to send those variables: docker-compose manage them in a way and kubernetes in an another way. (a matter of syntax)
So you can use the same image over compose and over kubernetes, but the syntax of sending the env variables will differ.
If you want them to presist no matter of the deployment and tool, you can always hardcode those env variables in the image itself, in another term, in your dockerfile that you used to build the image.
I dont recommend this way ofc, and it might not work for you in case you are using pre-built official images, but the below is an example of a dockerfile with env included.
FROM alpine:latest
# this is how you hardcode it
ENV VAR_1 foo
COPY helloworld.sh .
RUN chmod +x /helloworld.sh
CMD ["/helloworld.sh"]
If you want to move toward managing this in a much better way, you can use an .env file in your docker-compose to be able to update all the variables, especially when your compose have several apps that share the same variables.
app1:
image: ACRHOST/app1:latest
env_file:
- .env
And on kubernetes side, you can create a config map, link your pods to that configmap and then you can update the value of the configmap only.
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
kubectl create configmap <map-name> <data-source>
Also note that you can set the values in your configmap directly from the .env file that you use in docker, check the link above.
The docker-compose.yml file and the Kubernetes YAML file serve similar purposes; both explain how to create a container from a Docker image. The Compose file is only read when you're running docker-compose commands, though; the configuration there isn't read when deploying to Kubernetes and doesn't make any permanent changes to the image.
If something needs to be set as an environment variable but really is independent of any particular deployment system, set it as an ENV in your image's Dockerfile.
ENV VAR_1=foo
ENV VAR_2=bar
# and don't mention either variable in either Compose or Kubernetes config
If you can't specify it this way (e.g., database host names and credentials) then you need to include it in both files as you've shown. Note that some of the configuration might be very different; a password might come from a host environment variable in Compose but a Kubernetes Secret.
We have used the technique detailed here to expose host environment variables to Docker build in a secured fashion.
# syntax=docker/dockerfile:1.2
FROM golang:1.18 AS builder
# move secrets out of the build process (and docker history)
RUN --mount=type=secret,id=github_token,dst=/app/secret_github_token,required=true,uid=10001 \
export GITHUB_TOKEN=$(cat /app/secret_github_token) && \
<nice command that uses $GITHUB_TOKEN>
And this command to build the image:
export DOCKER_BUILDKIT=1
docker build --secret id=github_token,env=GITHUB_TOKEN -t cool-image-bro .
The above works perfectly.
Now we also have a docker-compose file running in CI that needs to be modified. However, even if I confirmed that the ENV vars are present in that job, I do not know how to assign the environment variable to the github_token named secret ID.
In other words, what is the equivalent docker-compose command (up --build, or build) that can accept a mapping of an environment variable with a secret ID?
Turns out I was a bit ahead of the times. docker compose v.2.5.0 brings support for secrets.
After having modified the Dockerfile as explained above, we must then update the docker-compose to defined secrets.
docker-compose.yml
services:
my-cool-app:
build:
context: .
secrets:
- github_user
- github_token
...
secrets:
github_user:
file: secrets_github_user
github_token:
file: secrets_github_token
But where are those files secrets_github_user and secrets_github_token coming from? In your CI you also need to export the environment variable and save it to the default secrets file location. In our project we are using Tasks so we added these too lines.
Note that we are running this task from our CI, so you could do it differently without Tasks for example.
- printenv GITHUB_USER > /root/project/secrets_github_user
- printenv GITHUB_TOKEN > /root/project/secrets_github_token
We then update the CircleCI config and add two environment variable to our job:
.config.yml
name-of-our-job:
environment:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
You might also need a more recent Docker version, I think they introduced it in a late 19 release or early 20. I have used this and it works:
steps:
- setup_remote_docker:
version: 20.10.11
Now when running your docker-compose based commands, the secrets should be successfully mounted through docker-compose and available to correctly build or run your Dockerfile instructions!
Is it possible to access machine environments inside dockerfile? I was thinking passing the SECRET as build ARG, like so:
docker-compose:
version: '3.5'
services:
service:
...
build:
...
args:
SECRET: ${SECRET}
...
dockerfile:
FROM image
ARG SECRET
RUN script-${SECRET}
Note: the container is build in kubernetes, I can not pass any arguments to the build command or perform any command at all.
Edit 1: It is okay to pass SECRET as ARG because this is not sensitive data. I'm using SECRETS to access micro service data, and I can only store data using secrets. Think of this as machine environment.
Edit 2: This was not a problem with docker but with the infrastructure that I was working with which does not allow any arguments to be passed to the docker build.
The secrets should be used during run time and provided by execution environment.
Also everything that is executing during a container build is written down as layers and available later to anyone who is able to get access to an image. That's why it's hard to consume secrets during the build in a secure way.
In order to address this, Docker recently introduced a special option --secret. To make it work, you will need the following:
Set environment variable DOCKER_BUILDKIT=1
Use the --secret argument to docker build command
DOCKER_BUILDKIT=1 docker build --secret id=mysecret,src=mysecret.txt...
Add a syntax comment to the very top of your Docker file
# syntax = docker/dockerfile:1.0-experimental
Use the --mount argument to mount the secret for every RUN directive that needs it
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
Please note that this needs Docker version 18.09 or later.
ARG is a build time argument. You want to keep Secrets secret and not write them in the artifacts. Keep secrets in external environment variables or in external files.
docker run -e SECRET_NAME=SECRET_VALUE
and in docker-compose:
services:
app-name:
environment:
- SECRET_NAME=YOUR_VALUE
or
services:
app-name:
env_file:
- secret-values.env
Kubernetes
When you run exactly the same container image in Kubernetes, you mount the secret from a Secret object.
containers:
- name: app-name
image: app-image-name
env:
- name: SECRET_NAME
valueFrom:
secretKeyRef:
name: name-of-secret-object
key: token
Yes, to passing secret data as ARG if you need to access the secret during the container build; you have no (!?) alternative.
ARG values are only available for the duration of the build so you need to be able to trust the build process and that it is cleaned up appropriately at its conclusion; if a malicious actor were able to access the build process (or after the fact), it could access the secret data.
It's curious that you wish to use the secret as script-${SECRET} as I assumed the secret would be used to access an external service. Someone would be able to determine the script name from the resulting Docker image and this would expose your secret.
I have a docker-compose file that allows me to pass the environment variables as a file (.env file). As I have multiple ENV variables, Is there any option in Dockerfile like env_file in docker-compose for passing multiple environment variables during docker build?
This is the docker-compose.yml
services:
web:
image: "node"
links:
- "db"
env_file: "env.app"
AFAIK, there is no such way to inject environment variables using a file during the build step using Dockerfile. However, in most cases, people do end up using an entrypoint script & injecting variables during the docker run or docker-compose up.
In case it's a necessity you might need to write a shell wrapper which will change the values in the Dockerfile dynamically by taking a key-value pair text file as an input or make it something as below but the ENV file name need to be included in Dockerfile.
COPY my-env-vars /
RUN export $(cat my-env-vars | xargs)
It's an open issue - https://github.com/moby/moby/issues/28617
PS - You need to be extra careful while using this approach because the secrets are baked into the image itself.
I have my app inside a container and it's reading environment variables for passwords and API keys to access services. If I run the app on my machine (not inside docker), I just export SERVICE_KEY='wefhsuidfhda98' and the app can use it.
What's the standard approach to this? I was thinking of having a secret file which would get added to the server with export commands and then run a source on that file.
I'm using docker & fig.
The solution I settled on was the following: save the environment variables in a secret file and pass those on to the container using fig.
have a secret_env file with secret info, e.g.
export GEO_BING_SERVICE_KEY='98hfaidfaf'
export JIRA_PASSWORD='asdf8jriadf9'
have secret_env in my .gitignore
have a secret_env.template file for developers, e.g.
export GEO_BING_SERVICE_KEY='' # can leave empty if you wish
export JIRA_PASSWORD='' # write your pass
in my fig.yml I send the variables through:
environment:
- GEO_BING_SERVICE_KEY
- JIRA_PASSWORD
call source secret_env before building
docker run provides environment variables:
docker run -e SERVICE_KEY=wefsud your/image
Then your application would read SERVICE_KEY from the environment.
https://docs.docker.com/reference/run/
In fig, you'd use
environment:
- SERVICE_KEY: wefsud
in your app spec. http://www.fig.sh/yml.html
From a security perspective, the former solution is no worse than running it on your host if your docker binary requires root access. If you're allowing 'docker' group users to run docker, it's less secure, since any docker user could docker inspect the running container. Running on your host, you'd need to be root to inspect the environment variables of a running process.