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
-
First make sure that the dependencies are installed:
apt install git make m4 highlight tree
-
Then get the code from GitLab:
git clone \
https://gitlab.com/docker-scripts/ds \
/opt/docker-scripts/ds
ls /opt/docker-scripts/ds/ -
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.
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
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 commandfail
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.
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.