Passa al contenuto principale

A Basic Example

1. About Docker Scripts

docker-scripts are Bash scripts that are used to manage Docker containers.

Docker commands have lots of arguments and options and they tend to be very long. It is tedious and error prone to type thesecommands manually, so recording them in Bash scripts is a necessity. However the scripts of different containers also have a lot of things in common, which would be nice to abstragate and reuse. And this is one of the things that docker-scripts does. It is like a framework that makes building and using such scripts easy.

2. Install Docker Scripts

  1. First make sure that the dependencies are installed:

    apt install git make m4 highlight tree
  2. Then get the code from GitLab:

    git clone \
    https://gitlab.com/docker-scripts/ds \
    /opt/docker-scripts/ds

    ls /opt/docker-scripts/ds/
  3. Then install it:

    cd /opt/docker-scripts/ds/
    make install

    ds
    ds -h
    man ds

3. Create a container

Let's start with a very simple example. For the time being let's work on the directory /var/test/:

mkdir /var/test
cd /var/test/

3.1 Create the scripts

Let's use the directory scripts1/ for the scripts of this container:

mkdir scripts1
echo 'include(bookworm)' > scripts1/Dockerfile
echo 'APP=scripts1' > scripts1/settings.sh

tree
cat scripts1/Dockerfile
cat scripts1/settings.sh

The Dockerfile defines the image of the container. It is like a normal Dockerfile, except that before being used to build the image, it is preprocessed by m4 in order to include other files, etc. The file bookworm is provided by docker-scripts:

ls /opt/docker-scripts/ds/src/dockerfiles/
nano /opt/docker-scripts/ds/src/dockerfiles/bookworm
nano /opt/docker-scripts/ds/src/dockerfiles/noble

3.2 Initialize the container

Let's use ds init to initialize the container's directory:

ds init scripts1 @./app1

tree
nano app1/settings.sh

The command ds init creates a directory for the container at ./app1/, copies settings.sh to it, and modifies the values of APP, IMAGE and CONTAINER. The setting APP tells where the scripts for this container are located, IMAGE is the name of the docker image that will be built for this container (set by default to the name of the scripts directory), and CONTAINER is the name of the container (set by default to the name of the container's directory).

We can edit settings.sh and modify IMAGE and CONTAINER, if we want, but we should not change the value of APP, otherwise the framework will not be able to find the correct Dockerfile and any other scripts related to this container.

note

Because the docker-scripts are usually used to manage dockerized applications, like Moodle, NextCloud, Gitea, etc. the directory of the scripts is also called application directory, and the setting/variable that keeps its location is called APP.

3.3 Build the container

The command ds make will build the image of the container, create the container, and start it. But first we need to be located in the directory of the container:

cd app1/
ds make
ds info
docker ps | less
docker image ls

The command ds make, as well as all the other ds commands, gets the name of the image and the container from settings.sh on the current directory (by including it as a bash script). That's why we need to be on the container directory before using any ds command.

4. Play around

4.1 Check the logs

The output of the ds commands, besides being displayed on the screen, is also saved on a log file, for later inspection:

tree
less -r logs/app1-20250224.out

4.2 Get a shell

With ds shell we can get a shell inside the container:

ds shell

pwd
ls -lR
cat settings.txt
touch test.txt
ls
exit

ls
rm test.txt

The directory of the container (/var/test/app1/) is mounted inside the container at /host. This helps sharing of data between the container and the host, for example in the case of backup/restore. It also helps the scripts that run inside the container to easily load the container settings (by sourcing /host/settings.sh).

4.3 Change the prompt

Just for fun, let's modify the prompt of the shell inside the container:

ds inject change-prompt.sh

ds shell
ls
exit

nano /usr/local/lib/ds/inject/change-prompt.sh
ls /usr/local/lib/ds/
ls /usr/local/lib/ds/inject/
ls /opt/docker-scripts/ds/src/inject/

The command ds inject copies the script inside the container, and executes it from there.

4.4 Use debug options

ds stop
ds start
ds -d stop
ds -d start
ds -d restart
ds -d exec ls -l /
ds -d shell

The option -d shows the docker commands that are being used by ds. This kind of debugging can also be enabled using the environment variable SHOW_DOCKER_COMMANDS:

SHOW_DOCKER_COMMANDS=true ds exec ls -l

export SHOW_DOCKER_COMMANDS=true
ds exec ls -l
ds shell
exit

unset SHOW_DOCKER_COMMANDS
ds exec ls -l

Use the option -x to see all the steps that ds takes while executing a command:

ds -x make
ds -x -d make
ds -x make > output.log

The options -x and -d should come right after ds and before anything else. When they are used together, -x should come before -d.

4.5 Custom configuration

Notice that we have lost the customization of the prompt that we did before (with ds inject):

ds shell
exit

This is because the command ds make creates a new container from the image and the customizations that we made to the old container are lost.

However, the command ds make calls also ds config after ds create:

ds -x make > output.log

We can customize ds config to fix the prompt, like this:

mkdir -p cmd

cat <<EOF > cmd/config.sh
cmd_config() {
ds inject change-prompt.sh
}
EOF

nano cmd/config.sh
ds make
ds shell

The local file cmd/config.sh, which defines the function cmd_config(), is included automatically by ds and overrides the default function cmd_config() of the framework:

nano /opt/docker-scripts/ds/src/cmd/config.sh

The default function cmd_config() is just empty, so it's ok to override it with a local configuration script.

5. Build a second container

5.1 Initialize the directory

note

The assumption is that we are currently on the directory /var/test/app1/.

To build another container based on scripts1 you should first initialize a directory for it, like this:

ds init ../scripts1 @../app2

tree ..

To understand the strange syntax of this command, let's have a look at its help:

ds init

Normally, the scripts are placed under the directory /opt/docker-scripts/ and the containers under the directory /var/ds/. Relative or absolute directories are used rarely, for example for testing.

5.2 Customize settings

ls ../app2/
nano ../app2/settings.sh

The name of the directory has been set as the name of the CONTAINER, but we can change it as long as it is unique among all the docker containers.

The name of the IMAGE can also be changed, but usually the name of the scripts directory is a good setting.

However we should not change the value of APP, unless we move the scripts directory to another location.

5.3 Build the container

ds @../app2 info
ds @/var/test/app2 make

If the first argument of ds starts with '@', it switches to the specified container directory before interpreting the rest of the command. However it is usually more convenient to make a cd to the container's directory, before giving any ds commands related to that container.

cd ../app2
ds info
ds shell

5.4 Fix the configuration

Notice that the prompt is the standard (not-customized) one. To customize it we can do the same thing that we did for the first container:

mkdir cmd
cp ../app1/cmd/config.sh cmd/
tree
cat cmd/config.sh

ds make

ds shell

Notice however that we are making the same config customization for both containers. If we need to make the same customization for all the containers of this type (containers that are based on scripts1), it is better to make this customization to the scripts:

cd ../scripts1/
mkdir cmd
cp ../app1/cmd/config.sh cmd/
rm -rf ../app1/cmd/
rm -rf ../app2/cmd/

tree /var/test/
cat cmd/config.sh

Let's check that it works:

ds @../app1 make
ds @../app1 shell
ds @../app2 make
ds @../app2 shell

6. Add a new ds command

We have already seen how to override the existing command ds config. The same way we can create a new ds command.

6.1 Create "ds hello"

As an example, let's create the command ds hello that just prints a greeting from the container. We should create it on the scripts directory, so that it can be used by all the containers.

On the directory /var/test/scripts1/ create the file cmd/hello.sh:

cd /var/test/scripts1/

cat << '_EOF_' > cmd/hello.sh
cmd_hello() {
local name=$1
[[ -n $name ]] || fail "Usage: hello <name>"

echo "Hello $name from container '$CONTAINER'!"
}
_EOF_
tree
nano cmd/hello.sh

By convention, we create a bash file that is named after the command, in the subdirectory cmd/, and inside this file we declare a function that is named after the command and has the prefix 'cmd_'. In this example it is cmd_hello().

The file cmd/hello.sh doesn't need to be executable because it is sourced (included) by ds.

Let's try the ds hello command:

ds @../app1 hello
ds @../app1 hello Foo
cd ../app2/
ds hello Foo

Notice that:

  • The arguments that are given after ds hello are passed to the function cmd_hello. Actually it requires an argument and fails if this argument is missing. The command fail is an auxiliary function defined by DS.

  • The variables defined in the file settings.sh of the container are available as global variables on the function of the command. In this example we are using the variable $CONTAINER.

6.2 Define help

ds help

You will also see hello listed by the end of the help.

To add a description to its help entry we can define the function cmd_hello_help() on /var/test/scripts1/cmd/hello.sh.

cd /var/test/scripts1/

cat << '_EOF_' > cmd/hello.sh
cmd_hello_help() {
cat <<EOF
hello <name>
Print a greeting for <name> from the container.

EOF
}

cmd_hello() {
local name=$1
[[ -n $name ]] || fail "Usage:\n$(cmd_hello_help)"

echo "Hello $name from container '$CONTAINER'!"
}
_EOF_

By convention, we append '_help' to the name of the cmd function (cmd_hello). In this example the help function is using cat to output the text of the help, but you can also use echo or something else.

Let's try again:

ds help

6.3 Customize a command

Let's say that we want to customize the command ds hello for the container app2, so that besides the greeting it also displays some info about the container.

Let's create the file cmd/hello.sh inside /var/test/app2/:

cd /var/test/app2/
mkdir cmd

cat << '_EOF_' > cmd/hello.sh
rename_function cmd_hello scripts1_cmd_hello

cmd_hello() {
ds info
scripts1_cmd_hello "$@"
}
_EOF_

We are overriding the function cmd_hello() by defining it again. However, before doing that, we are saving the original function (defined by the scripts) by renaming it to scripts1_cmd_hello, so that we can use it inside the new function. Inside the new function we are calling the original one and we are passing all the arguments to it (if any) with "$@". We are also calling ds info to display some information about the container and its state.

note

To change the name of an existing function we are using the auxiliary function rename_function which is defined by the framework.

ls /opt/docker-scripts/ds/src/
nano /opt/docker-scripts/ds/src/auxiliary.sh

This is a dirty trick that we are using because Bash lacks proper OOP features. But as long as it gets the job done, who cares!

Let's try ds hello again:

ds hello
ds hello Foo

cd ../app1/
ds hello Foo

For the other container the output is unchanged.