I run a docker swarm and i use gitlab ci to do the build and deployment of the images, the biggest headache i face is incrementing the image version numbers in the deployment yaml.
So for example if i do a deploy on gitlab and build the relevant image like this:
docker build --no-cache --platform linux/amd64 -t myregistry/myimage:$CI_COMMIT_TAG -f docker/php-backend/Dockerfile .
I am creating the image version number by using the git tag, which works fine. I then transfer the latest deploy.yaml file to the server and make it run:
sudo docker stack deploy --with-registry-auth -c live-deploy.yaml my-stack-name
The issue here is, inside my live-deploy.yaml i have to manually update the image name with the new version that was built.
Is there a way (and so far i can't find it) to pass a variable into the yaml from the command line when deploying so it knows what version number to use? A bit like passing in environment variables with docker compose.
You can play with environment variables to acheive this automation. Example follows:
Sample docker-compose/stack file :-
version: '3.3'
services:
registry:
restart: always
image: ${MyImageName}
ports:
- 5000:5000
And when you want to deploy something pass env value along with the imperative command... your command modifies to :-
MyImageName=myregistry/myimage:$CI_COMMIT_TAG docker stack deploy --with-registry-auth -c live-deploy.yaml my-stack-name
You can even keep the image name constant and just have a variable just for the tag if that is the degree of automation required.
Related
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!
I have a few microservices. Jenkins builds these projects, creates docker images and publishes them to the artifactory.
I have another project for automation testing which is using these docker images.
We have a docker-compose file that has all the configuration of all microservice images.
Following is sample docker-compose
version: "1.0"
services:
my-service:
image: .../my-service:1.0.0-SNAPSHOT-1
container_name: 'my-service'
restart: always
volumes:
...
ports:
...
...
all these are working fine.
Now to update the image then I have to manually change the image tag (1.0.0-SNAPSHOT-2) in docker-compose.
This is an issue because this involves human intervention. Is there any way to pull the newest docker image without any change in docker-compose?
NOTE - I cannot create images with the latest tag. Getting issue when publishing image with the same name in the artifactory (unauthorized: The client does not have permission for manifest: Not enough permissions to delete/overwrite artifact).
Well What actually you can do is, use environment variables substitutions in cli commands (envsubst). Let me explain an escenario as example.
First in the docker-compose.yaml you define an environmet variable, as a tag of the container
version: "3"
services:
my-service:
image: .../my-service:$TAG
container_name: 'my-service'
restart: always
volumes:
...
ports:
...
...
Second, with cli command (or terminal) you define an environment variable, with you version. This part is important because here you add your version tag to the container (and you can execute bash commands to extract some id, or last git commit or what ever you want to execute as tag for the container, i give you some ideas)
export TAG=1.0.0-SNAPSHOT-1
export TAG="$(bash /path/to/script/tag.sh)"
export TAG="$(git log --format="%H" -n 1)"
And the third part and last one is for execute "envsubst" and then execute docker-compose.yaml to deploy your container. Note the pipe |, very important for execution.
envsubst < docker-compose.yaml | docker-compose up -d
link to envsubst
I use this format to deploy tagged containers in kubernetes, but the idea must be the same with docker compose.
envsubst < deployment.yaml | kubectl apply -f -
And change version to 3 in the docker-compose.yaml. Good luck
Are docker and docker-compose supposed to differ in their handling of .dockerignore?
I've got a Docker container which builds fine when I build it directly, e.g. via docker build -t mycontainer ./mycontainer, but it fails when I build it via docker-compose up. Relevant portion of the docker-compose.yml is
version: '3'
services:
mycontainer:
build: ./mycontainer
ports:
- "1234:1234"
expose:
- "1234"
By using docker run --rm -it --entrypoint=/bin/bash 240e1a06c8f5, where 240e1a06c8f5 is the last image before the build failure, I found that one of the files, ./mycontainer/mymodel/labels.rdata, wasn't being copied over by docker-compose up, but is copied by docker build. It's also close to a pattern in the .dockerignore, */*.RData.
Is this a difference between case-sensitivity in .dockerignore between docker-compose and docker build? Is it a difference in path handling? Is this a known bug? (or intended?)
Versions on MacOs:
$ docker --version
Docker version 18.09.1, build 4c52b90
$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01
I just tried using a case-insensitive .dockerignore entry on Windows and it did actually ignore the file, so it looks like your */*.RData will actually ignore your ./mycontainer/mymodel/labels.rdata file.
Try changing the extension of the file or changing the ignore entry. I'd change the filename, since that seems like the one-off here.
EDIT: This does sound like a bug. I'd file one on their Github Issues page since I don't see one there already
According to this and this GitHub issues, currently there is no native way how to supply multiple tags for a service's image when using docker-compose to build one or multiple images.
My use case for this would be to build images defined in a docker-compose.yml file and tag them once with some customized tag (e.g. some build no. or date or similar) and once as latest.
While this can be easily achieved with plain docker using docker tag, docker-compose only allows to set one single tag in the image key. Using docker tag together with docker-compose is not an option for me since I want to keep all my docker-related definitions in the docker-compose.yml file and not copy them over into my build script.
What would be a decent work-around to achieve setting of multiple tags with docker-compose and without having to hardcode/copy the image names first?
I have some nice and clean solution using environment variables (bash syntax for default variable value, in my case it is latest but you can use anything ), this is my compose:
version: '3'
services:
app:
build: .
image: myapp-name:${version:-latest}
build and push (if you need to push to the registry) with the default tag, change the version using environment variable and build and push again:
docker-compose build
docker-compose push
export version=0.0.1
docker-compose build
docker-compose push
You can also take the following approach:
# build is your actual build spec
build:
image: myrepo/myimage
build:
...
...
# these extend from build and just add new tags statically or from environment variables or
version_tag:
extends: build
image: myrepo/myimage:v1.0
some_other_tag:
extends: build
image: myrepo/myimage:${SOME_OTHER_TAG}
You can then just run docker-compose build and docker-compose push and you will build and push the correct set of tagged imaged
I came up with a couple of work-arounds of different complexity. They all rely on the assumption that ${IMAGE_TAG} stores the customized tag that represents e.g. a build no. and we want to tag all services' images with this tag as well as with latest.
grep the image names from the docker-compose.yml file
images=$(cat docker-compose.yml | grep 'image: ' | cut -d':' -f 2 | tr -d '"')
for image in $images
do
docker tag "${image}":"${IMAGE_TAG}" "${image}":latest
done
However, this is error prone if somebody adds a comment in docker-compose.yml which would e.g. look like # Purpose of this image: do something useful....
Build twice
Use ${IMAGE_TAG} as an environment variable in your docker-compose.yml file as described here in the first example.
Then, simply run the build process twice, each time substituting ${IMAGE_TAG} with a different value:
IMAGE_TAG="${IMAGE_TAG}" docker-compose build
IMAGE_TAG=latest docker-compose build
The second build process should be much faster than the first one since all image layers should still be cached from the first run.
Drawback of this approach is that it will flood your log output with two subsequent build processes for each single service which might make harder to search through it for something useful.
Besides, if you have any command in your Dockerfile which always flushes the build cache (e.g. an ADD command fetching from a remote location with auto-updating last-modified headers, adding files which are constantly updated by an external process etc.) then the extra build might slow things down significantly.
Parse image names from the docker-compose.yml file with some inline Python code
Using a real yaml parser in Python (or any other language such as Ruby or perl or whatever is installed on your system) is more robust than the first mentioned grep approach since it will not get confused by comments or strange but valid ways of writing the yml file.
In Python, this could look like this:
images=$(python3 <<-EOF # make sure below to indent with tabs, not spaces; or omit the "-" before "EOF" and use no indention at all
import yaml
content = yaml.load(open("docker-compose.build.yml"))
services = content["services"].values()
image_names = (service["image"].split(":")[0] for service in services)
print("\n".join(image_names))
EOF
)
for image in ${images}
do
docker tag ${image}:${IMAGE_TAG} ${image}:latest
done
Drawback of this approach is that the machine executing the build has to have Python3 installed, along with the PyYAML library. As already mentioned, this pattern could similarly be used with Python2 or any other programming language that is installed.
Get image names with combination of some docker commands
The following approach using some native docker and docker-compose commands (using go-templates) is a bit more complex to write but also works nicely.
# this should be set to something unique in order to avoid conflicts with other running docker-compose projects
compose_project_name=myproject.tagging
# create containers for all services without starting them
docker-compose --project-name "${compose_project_name}" up --no-start
# get image names without tags for all started containers
images=$(docker-compose --project-name "${compose_project_name}" images -q | xargs docker inspect --format='{{ index .RepoTags 0}}' | cut -d':' -f1)
# iterate over images and re-tag
for image in ${images}
do
docker tag "${image}":"${IMAGE_TAG}" "${image}":latest
done
# clean-up created containers again
docker-compose --project-name "${compose_project_name}" down
While this approach does not have any external dependencies and is more safe than the grep method, it might take a few more seconds to execute on large setups for creating and removing the containers (typically not an issues though).
As suggested by #JordanDeyton extends cannot by used anymore in Compose file format > 3 and the Extension fields capability added in the version 3.4 can replace it to achieve the same goal. Here is an example.
version: "3.4"
# Define common behavior
x-ubi-httpd:
&default-ubi-httpd
build: ubi-httpd
# Other settings can also be shared
image: ubi-httpd:latest
# Define one service by wanted tag
services:
# Use the extension as is
ubi-httpd_latest:
*default-ubi-httpd
# Override the image tag
ubi-httpd_major:
<< : *default-ubi-httpd
image: ubi-httpd:1
ubi-httpd_minor:
<< : *default-ubi-httpd
image: ubi-httpd:1.0
# Using an environment variable defined in a .env file for e.g.
ubi-httpd_patch:
<< : *default-ubi-httpd
image: "ubi-httpd:${UBI_HTTPD_PATCH}"
Images can be built now with all the defined tags
$ docker-compose build
# ...
$ docker images | grep ubi-httpd
# ubi-httpd 1 8cc412411805 3 minutes ago 268MB
# ubi-httpd 1.0 8cc412411805 3 minutes ago 268MB
# ubi-httpd 1.0.1 8cc412411805 3 minutes ago 268MB
# ubi-httpd latest 8cc412411805 3 minutes ago 268MB
There is now a built-in solution using buildx bake, released in v.0.7.0.
This feature was implemented following to my suggestion in https://github.com/docker/buildx/issues/396.
Docker comes bundled with buildx installed, however, if you are on Mac and running Docker Desktop the bundled buildx version is older at the time of writing this and you will need to install the correct version of buildx in addition to Docker.
Add the x-bake extension field to your docker-compose.yaml:
version: '3.9'
services:
my-app:
image: my-repo/my-image:latest
build:
context: .
dockerfile: Dockerfile
x-bake:
tags:
- my-repo/my-image:${MY_TAG_1}
- my-repo/my-image:${MY_TAG_2}
- my-repo/my-image:${MY_TAG_3}
- my-other-repo/my-image:${MY_TAG_1}
- my-other-repo/my-image:${MY_TAG_2}
- my-other-repo/my-image:${MY_TAG_3}
To build and tag the image run:
buildx bake --load
To build, tag and push to image to the repository or even to multiple repositories:
buildx bake --push
In my CI environment there are several build versions and I need to get a specific version for docker-compose:
registry.example.com/foo/core 0.1.3
registry.example.com/foo/core 0.2.2
... # multiple packages in several versions like this
The images are builded like this:
build:
stage: build
script:
...
- docker build -t $CI_REGISTRY_IMAGE:$VERSION .
- docker push $CI_REGISTRY_IMAGE:$VERSION
And they are pulled on deploy pipeline like this:
production:
stage: deploy
script:
- docker pull $CI_REGISTRY_IMAGE:$VERSION
I'm also using docker-compose to start all microservices from that images:
$ docker-compose up -d
But now that is my problem. Where can I store which version of each image should be used? Hardcoded it looks like this - but this will always start 0.2.2, although the deploy pipeline could pull a different version, like 0.1.3:
core:
container_name: core
image: 'registry.example.com/foo/core:0.2.2'
restart: always
links:
- 'mongo_live'
environment:
- ROOT_URL=https://example.com
- MONGO_URL=mongodb://mongo_live/example
But the version should be better set as a variable. So I would think that on deploy I have to store the current $VERSION-value somewhere. On running docker-compose the version value should be read to get the correct version as the latest version is not always the selected one.
If you pass it as a variable, you'd store it where ever you define your environment variables in your CI environment. You can also store the values in a .env which is described here.
Using the variable defined in the environment (or .env file) would have a line in the docker-compose.yml like:
image: registry.example.com/foo/core:${VERSION}
Personally, I'd take a different approach and let the registry server maintain the version of the image with tags. If you have 3 versions for dev, stage, and prod, you can build your registry.example.com/foo/core:0.2.2 and then tag that same image as registry.example.com/foo/core:dev. The backend image checksum can be referenced by multiple tags and not take up additional disk space on the registry server or the docker hosts. Then in your dev environment, you'd just do a docker-compose pull && docker-compose up -d to grab the dev image and spin it up. The only downside of this approach is that the tag masks which version of the image is currently being used as dev, so you'll need to track that some other way.
Tagging an image in docker uses the docker tag command. You would run:
docker tag registry.example.com/foo/core:${VERSION} registry.example.com/foo/core:dev
docker push registry.example.com/foo/core:${VERSION}
docker push registry.example.com/foo/core:dev
If the registry already has a tag for registry.example.com/foo/core:dev it gets replaced with the new tag pointing to the new image id.