dockerFile CMD on conditional - docker

I have two containers a Flask and a Celery container. Both containers use similar configs except the CMD. How can I change the CMD to change based on an env variable?
if [ $CONTAINER = 'flask' ] ; then \
CMD \["uwsgi", "--ini", "uwsgi.ini"\] ; else \
CMD \["celery", "--app=flask_project.celery_app.celery", "worker"\]; \
fi

Instead of having two separate CMDs, have one CMD that can run either thing.
In shell syntax, this might look like:
if [ "$CONTAINER" = flask ]; then
exec wsgi --init uwsgi.ini "$#"
else
exec celery --app=flask_project.celery_app.celery worker "$#"
fi
...in a Dockerfile, this might look like:
CMD [ "/bin/sh", "-c", "if [ \"$CONTAINER\" = flask ]; then exec uwsgi --ini uwsgi.ini \"$#\"; else exec celery --app=flask_project.celery_app.celery worker \"$#\"; fi" ]
The exec forces the copy of /bin/sh to replace itself in-place with wsgi or celery, so it doesn't exist in the process tree except for the short period it needs to make a decision.

Instead of having two separate CMDs, have the CMD do the most common thing, and override the CMD if you need to do the other thing.
For example, you might say that the "most common" thing your container will do is to launch the Flask server
CMD ["uwsgi", "--ini", "uwsgi.ini"]
and if you just run the container it will do that
docker run -d --name web --net my-net -p 5000:5000 my-image
but you can also provide an alternate command after the docker run image name, and this will replace the Dockerfile CMD.
docker run -d --name celery --net my-net my-image \
celery --app=flask_project.celery_app.celery worker
If you're in a Docker Compose setup, you can use the command: setting to specify this override, like
version: '3.8'
services:
redis:
image: redis
web:
build: .
ports: ['5000:5000']
depends_on: [redis]
celery:
build: .
command: celery --app=flask_project.celery_app.celery worker
depends_on: [redis]

Related

Start a container in interactive shell in docker compose [duplicate]

Is there any way to start an interactive shell in a container using Docker Compose only? I've tried something like this, in my docker-compose.yml:
myapp:
image: alpine:latest
entrypoint: /bin/sh
When I start this container using docker-compose up it's exited immediately. Are there any flags I can add to the entrypoint command, or as an additional option to myapp, to start an interactive shell?
I know there are native docker command options to achieve this, just curious if it's possible using only Docker Compose, too.
You need to include the following lines in your docker-compose.yml:
version: "3"
services:
app:
image: app:1.2.3
stdin_open: true # docker run -i
tty: true # docker run -t
The first corresponds to -i in docker run and the second to -t.
The canonical way to get an interactive shell with docker-compose is to use:
docker-compose run --rm myapp
(With the service name myapp taken from your example. More general: it must be an existing service name in your docker-compose file, myapp is not just a command of your choice. Example: bash instead of myapp would not work here.)
You can set stdin_open: true, tty: true, however that won't actually give you a proper shell with up, because logs are being streamed from all the containers.
You can also use
docker exec -ti <container name> /bin/bash
to get a shell on a running container.
The official getting started example (https://docs.docker.com/compose/gettingstarted/) uses the following docker-compose.yml:
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"
After you start this with docker-compose up, you can shell into either your redis container or your web container with:
docker-compose exec redis sh
docker-compose exec web sh
docker-compose run myapp sh should do the deal.
There is some confusion with up/run, but docker-compose run docs have great explanation: https://docs.docker.com/compose/reference/run
If anyone from the future also wanders up here:
docker-compose exec service_name sh
or
docker-compose exec service_name bash
or you can run single lines like
docker-compose exec service_name php -v
That is after you already have your containers up and running.
The service_name is defined in your docker-compose.yml file
Using docker-compose, I found the easiest way to do this is to do a docker ps -a (after starting my containers with docker-compose up) and get the ID of the container I want to have an interactive shell in (let's call it xyz123).
Then it's a simple matter to execute
docker exec -ti xyz123 /bin/bash
and voila, an interactive shell.
This question is very interesting for me because I have problems, when I run container after execution finishes immediately exit and I fixed with -it:
docker run -it -p 3000:3000 -v /app/node_modules -v $(pwd):/app <your_container_id>
And when I must automate it with docker compose:
version: '3'
services:
frontend:
stdin_open: true
tty: true
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- /app/node_modules
- .:/app
This makes the trick: stdin_open: true, tty: true
This is a project generated with create-react-app
Dockerfile.dev it looks this that:
FROM node:alpine
WORKDIR '/app'
COPY package.json .
RUN npm install
COPY . .
CMD ["npm", "run", "start"]
Hope this example will help other to run a frontend(react in example) into docker container.
I prefer
docker-compose exec my_container_name bash
If the yml is called docker-compose.yml it can be launched with a simple $ docker-compose up. The corresponding attachment of a terminal can be simply (consider that the yml has specified a service called myservice):
$ docker-compose exec myservice sh
However, if you are using a different yml file name, such as docker-compose-mycompose.yml, it should be launched using $ docker-compose -f docker-compose-mycompose.yml up. To attach an interactive terminal you have to specify the yml file too, just like:
$ docker-compose -f docker-compose-mycompose.yml exec myservice sh
A addition to this old question, as I only had the case last time. The difference between sh and bash. So it can happen that for some bash doesn't work and only sh does.
So you can:
docker-compose exec CONTAINER_NAME sh
and in most cases: docker-compose exec CONTAINER_NAME bash
use.
If you have time. The difference between sh and bash is well explained here:
https://www.baeldung.com/linux/sh-vs-bash
You can do docker-compose exec SERVICE_NAME sh on the command line. The SERVICE_NAME is defined in your docker-compose.yml. For example,
services:
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"
The SERVICE_NAME would be "zookeeper".
According to documentation -> https://docs.docker.com/compose/reference/run/
You can use this docker-compose run --rm app bash
[app] is the name of your service in docker-compose.yml

Starting docker containers

I have a docker-compose.yml file that starts two services: amazon/dynamodb-local on 8000 port and django-service. django-service runs tests that are dependent on dynamodb-local.
Here is working docker-compose.yml:
version: '3.8'
services:
dynamodb-local:
image: "amazon/dynamodb-local:latest"
container_name: dynamodb-local
ports:
- "8000:8000"
django-service:
depends_on:
- dynamodb-local
image: django-service
build:
dockerfile: Dockerfile
context: .
env_file:
- envs/tests.env
volumes:
- ./:/app
command: sh -c 'cd /app && pytest tests/integration/ -vv'
Now I need to run this without docker-compose, only using docker itself. I try to do following:
docker network create -d bridge net // create a network for dynamodb-local and django-service
docker run --network=net --rm -p 8000:8000 -d amazon/dynamodb-local:latest // run cont. att. to network
docker run --network=net --rm --env-file ./envs/tests.env -v `pwd`:/app django-service /bin/sh -c 'env && cd /app && pytest tests/integration -vv'
I can see that both services start, but I can't connect to the dynamo-db.
Where is the problem? Any comment or help is appreciated!
Through the docker-compose.yml, the amazon/dynamodb-local container has a name defined (container_name: dynamodb-local, If we do not set this property, docker-compose will use the service's name as container name). This enables other containers in the same network to address the container through its name.
In the docker-run command, we do not set an explicit container name. We can set an explicit container name by executing docker run ... --name dynamodb-local .... More details can be found in the corresponding docker run documentation.

What does the "." in "docker run <image> . " do?

I have 2 commands:
docker run -d -p 5000:8080 ${image_name} .
and
docker run -d -p 5000:8080 ${image_name}
The only difference between these two commands is the period at the end. What is the purpose of the period? I understand that it signifies the current directory, but what is its use in a command like this?
Arguments after the image name are passed to the image's entrypoint, so it depends on the default ENTRYPOINT of the image. Often, the entrypoint is bash, so running
docker run -d -p 5000:8080 ${image_name} .
Is like running bash ..
The fact that you are publishing ports in your docker run command makes me think that the image runs a server. Let's say the entrypoint of your image is python server.py. Then the command
docker run -d -p 5000:8080 ${image_name} .
is akin to running python server.py .
and the command
docker run -d -p 5000:8080 ${image_name}
is akin to running python server.py (note the absence of the dot).

Netcat (nc / ncat) cannot be interrupted in Docker

I have a service that does some tasks and then opens a port so that other services know it's finished. I use nc to do that: nc -l -k -p 1337. I use docker-compose to manage services.
When shutting down the services, the service running nc always takes several seconds to close while it should be instant. I think the process doesn't interrupt and docker has to kill it. If I run nc on the same service via docker-compose run I cannot interrupt the process via Ctrl+C.
When running nc locally it can instantly be terminated via Ctrl+C.
How can I create a service running nc -l -k -p 1337 which can be interrupted?
Dockerfile
FROM ruby:2.6.3-alpine
RUN apk add --no-cache netcat-openbsd
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
entrypoint.sh
#!/bin/sh
# ...
nc -l -k -p 1337
docker-compose.yml
services:
nc:
build:
context: .
dockerfile: Dockerfile
docker-compose up --build
OR:
entrypoint.sh
#!/bin/sh
# ...
exec "$#"
docker-compose.yml
services:
nc:
build:
context: .
dockerfile: Dockerfile
command: nc -l -k -p 1337
docker-compose up --build
docker-compose run --rm nc nc -l -k -p 1337
Assuming nc actually responds to signals under normal circumstances, you need to:
Use exec
Use the list version of command.
So in the first case, add an exec to the shell script.
And in the second case, probably need command: ["nc", "-l", "-k", "-p", "1337"] in the compose file.
See https://hynek.me/articles/docker-signals/ for full checklist.

How can I run two instances of a command in a container?

I am trying to write a docker-compose file that references a Dockerfile in the same directory. The purpose of this docker-compose file is to run the command htop when I build my Dockerfile image it runs htop perfectly fine and I can pass arguments to an entry point. Whenever I go to try to run docker-compose up it starts the htop instances but then exits immediately. Is there anyway I can open two terminals or two containers and each container be running an htop instance?
Dockerfile:
FROM alpine:latest
MAINTAINER anon
RUN apk --no-cache add \
htop
ENTRYPOINT ["htop"]
docker-compose.yml
version: '3'
services:
htop_one:
build: .
environment:
TERM: "linux"
htop_two:
build: .
environment:
TERM: "linux"
Any help would be greatly appreciated!
The immediate problem is a terminal incompatibility. You run this from a terminal that is unknown to the software in the docker image.
The second problem, of the containers exiting immediately, could be fixed by using a proper init like tini:
Dockerfile:
FROM alpine:latest
MAINTAINER anon
RUN apk --no-cache add \
htop\
tini
ENTRYPOINT ["/sbin/tini", "--"]
docker-compose.yaml:
version: '3'
services:
htop_one:
build: .
environment:
TERM: "linux"
command: ["top"]
htop_two:
build: .
environment:
TERM: "linux"
command: ["top"]
To run the two services in parallel, as they each need a controlling terminal, you would run, from two different terminals:
docker-compose up htop_one
and
docker-compose up htop_two
respectively.
Note this is creating two containers from the same image. Each docker-compose service is, of course, run in a separate container.
If you'd like to run commands in the same container, you could start a service like
docker-compose up myservice
and run commands in it:
docker exec -it <container_name> htop
from different terminals, as many times as you'd like.
Not also that you can determine container_name via docker container ls and you can also set the container name from the docker-compose file,
On the issue of your htop command exiting, thus causing your docker container to exit.
This is normal behavior for docker containers. The htop is most likely exiting because it can't figure out the terminal when in a docker image, as #petre mentioned. When you run your docker image, be sure to use the -i option for an interactive session.
docker run -it MYIMAGE htop
To change the docker auto-exit behavior, do something like this in your Dockerfile:
CMD exec /bin/sh -c "trap : TERM INT; (while true; do MYCOMMAND; sleep 1000; done) & wait"
This runs your MYCOMMAND command over and over again, but allows the container to be stopped when you want. You can run a docker exec -it MYCONTAINER sh when you want to do other things in that same container.
Also, if you happen to be running docker in Windows, then prefix a winpty to the docker command like: winpty docker ... so it can get the terminal correct.

Resources