Skip to content

deck_build

Simon edited this page Feb 17, 2019 · 76 revisions


deck-build is a powerful but very simple and tiny bash library to build custom docker images. It is based on build plans. A plan is nothing more than a bundle (folder) of a Dockerfile and a shell script.

One interesting feature is adding a (sudo enabled/disabled) user to image with one line of code. Another is configuring user specific python environments (as a substitute for things like pyenv). And it's very easy to add custom features: Add a shell script to the plan directory. That's all.

But deck-build does NOT change either your usual Dockerfile setup or the image build process. Integrating the related mechanisms is completely optional. To use a plan you only need to add a few commands to the (existing) Dockerfile and run the docker build process with deck-build.sh. Again, there is no limitation: deck-build.sh accepts and passes all docker build arguments.

Want to start immediately?
Install deck-build and jump to the Examples section.

Want to learn more about architecture and components first?
Jump to the Inside section.

Contents

Installation

mkdir -p ~/deck/build ~/deck/plan
cd ~/deck
git clone https://github.com/flexos-io/deck-build.git build/
ln -s build/deck-build.sh
./deck-build.sh   # should print help messages

Usage

Using deck-build requires only three little steps:

  1. Create your plan (Dockerfile + optional shell scripts + optional data)
  2. Add kit and plan to the Dockerfile (ARG + ADD + RUN)
  3. Run image build process with deck-build.sh (that executes docker build)

Example: Hello World

cd ~/deck/plan                # see "Installation"
# 1. CREATE PLAN
mkdir helloworld
vi helloworld/main.sh         # create helloworld/main.sh, see below
# 2. ADD KIT AND PLAN
vi helloworld/Dockerfile      # create helloworld/Dockerfile, see below
# 3. BUILD IMAGE
cd ~/deck
# use the plan to build the image "deckbuild/helloworld:0.1.0" 
#   deck-build.sh runs "docker build"
#   you can add any "docker build" argument after "--", e.g. "... -- --no-cache"
./deck-build.sh -t deckbuild/helloworld:0.1.0 -p plan/helloworld/
# create and run a container using the new image
#   prints "Hello World" and exits
docker run --rm deckbuild/helloworld:0.1.0

helloworld/main.sh:

#!/bin/bash
# This script runs INSIDE the image during build process.

# import kit (always needed)
#   imports green()
#   see "Inside Kit" for the full list of kit functions
. ${DECKBUILD_KIT}/main.sh

# Please note:
#   You don't need to use kit functions like green(), they are completely optional.
#   Write your shell scripts as you like it!

# use the kit function green() to print a stderr message during build process
green "Creating helloworld script"

# create the helloworld script INSIDE the image
echo -e "#!/bin/bash\necho 'Hello World'" > /root/helloworld.sh

This shell script is the full plan used in the following Dockerfile:

helloworld/Dockerfile:

# define any base image, e.g. debian slim
FROM debian:9.5-slim

## begin: deck-build initialization (always needed)
# import build variables set by deck-build.sh
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
# download and extract the kit to the image
#   unfortunately ADD cannot unpack remote archives (https://github.com/moby/moby/issues/2369#issuecomment-269712515)
#   storing the kit on local filesystem is also possible
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz
## end: deck-build initialization

## use the plan:
# 1.) copy plan (only one shell script in this case) to image
COPY ./main.sh ${DECKBUILD_PLANT}/
# 2.) run plan (shell script)
RUN bash ${DECKBUILD_PLANT}/main.sh

## run the hello-world script created by plan
CMD ["bash", "/root/helloworld.sh"]

Please note: For this example you need a working internet connection to access GitHub. But for daily usage internet is not mandatory. See Define The Kit.

Example: Custom User

cd ~/deck/plan            # see "Installation"
# 1. CREATE PLAN
mkdir user
vi user/main.sh           # create user/main.sh, see below
# 2. ADD KIT AND PLAN
vi user/Dockerfile        # create user/Dockerfile, see below
# 3. BUILD IMAGE
cd ~/deck
./deck-build.sh -t deckbuild/user:0.1.0 -p plan/user/
# create and run a container using the new image
#   prints informations about foo user and exits
docker run --rm deckbuild/user:0.1.0

user/main.sh:

#!/bin/bash
# This script runs INSIDE the image during build process.

# import kit (always needed)
#   imports blue(), installUser() and installSudoUser()
#   see "Inside Kit" for the full list of kit functions
. ${DECKBUILD_KIT}/main.sh

blue "Adding user foo with UID 1000 to image"
installUser foo 1000                # user=foo, group=foo, uid=1000, gid=1000
blue "Enabling sudo for user foo"
installSudoUser foo

blue "Adding user bar with UID 1001 to image"
installUser bar 1001 /home/bar bargrp 2000   # group=bargrp, gid=2000

This shell script is the full plan used in the following Dockerfile:

user/Dockerfile:

FROM debian:9.5-slim

## begin: deck-build initialization (always needed)
# import build variables set by deck-build.sh
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
# download and extract the kit to the image
#   storing the kit on local filesystem is also possible
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz
## end: deck-build initialization

## install sudo (needed by plan)
RUN apt-get -y update && apt-get -y --no-install-recommends install sudo

## use the plan:
# 1.) copy plan (only one shell script in this case) to image
COPY ./main.sh ${DECKBUILD_PLANT}/
# 2.) run plan (shell script)
RUN bash ${DECKBUILD_PLANT}/main.sh

## print informations about foo user and exit
CMD ["id", "foo"]

Please note: For this example you need a working internet connection to access GitHub. But for daily usage internet is not mandatory. See Define The Kit.

Example: Python

cd ~/deck/plan                     # see "Installation"
# 1. CREATE PLAN
mkdir python
vi python/foo_requirements.txt     # create python/foo_requirements.txt, see below
vi python/root_requirements.txt    # create python/root_requirements.txt, see below
vi python/main.sh                  # create python/main.sh, see below
# 2. ADD KIT AND PLAN
vi python/Dockerfile               # create python/Dockerfile, see below
# 3. BUILD IMAGE
cd ~/deck
./deck-build.sh -t deckbuild/python:0.1.0 -p plan/python/
# create and run a container using the new image
#   lists foo's python packages and exits
docker run --rm --user foo deckbuild/python:0.1.0
#   lists root's python packages and exits
docker run --rm deckbuild/python:0.1.0

python/foo_requirements.txt:

retrying>1,<2
simplejson>3,<4

python/root_requirements.txt:

pendulum>2,<3

python/main.sh:

#!/bin/bash
# This script runs INSIDE the image during build process.

# import kit (always needed)
#   imports stderr(), blue(), installUser(), sudof() and installPyPkgs()
#   see "Inside Kit" for the full list of kit functions
. ${DECKBUILD_KIT}/main.sh

stderr "Adding user foo with UID 1000 to image"
installUser foo 1000    # user=foo, group=foo, uid=1000, gid=1000

blue "Installing python packages for user foo"
# sudof() is a wrapper to run functions with sudo:
#   sudof <user> <bash_function> [<function_parameters>]
# installPyPkgs() installs python packages into user's home
# directory (PIP_USER=1)
sudof foo installPyPkgs ${DECKBUILD_PLANT}/foo_requirements.txt

blue "Installing python packages for user root"
# install other packages for user root: no sudof() needed
installPyPkgs ${DECKBUILD_PLANT}/root_requirements.txt

The combination of main.sh and the two requirements.txt files is the full plan used in the following Dockerfile:

python/Dockerfile:

FROM python:3-stretch

## begin: deck-build initialization (always needed)
# import build variables set by deck-build.sh
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
# download and extract the kit to the image
#   storing the kit on local filesystem is also possible
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz
## end: deck-build initialization

## install sudo (needed by plan)
RUN apt-get -y update && apt-get -y --no-install-recommends install sudo

## use the plan:
# 1.) copy plan (one shell script and two textfiles in this case) to image
COPY ./foo_requirements.txt ${DECKBUILD_PLANT}/
COPY ./root_requirements.txt ${DECKBUILD_PLANT}/
COPY ./main.sh ${DECKBUILD_PLANT}/
# 2.) run plan (the shell script that uses the textfiles as input)
RUN bash ${DECKBUILD_PLANT}/main.sh

## print user's pip packages and exit
CMD ["pip", "list"]

Please note: For this example you need a working internet connection to access GitHub. But for daily usage internet is not mandatory. See Define The Kit.

Inside: Architecture

1. User creates plan

As described in insides, a plan is a directory that contains at least a Dockerfile and a shell script. But plans can also be more complex. For example, it's possible to integrate functions with the help of plan artefacts that were created by the user or the community.

2. User runs deck-build

deck-build adds his internal library called kit and some settings to the build context and executes docker build.

Please note: deck-build.sh doesn't change the known build process! It accepts and passes all docker build arguments and uses the well-known Dockerfile to tie the parts. The process is straightforward and without any magic: a) User writes shell scripts. b) Dockerfile COPYs the scripts into the image. c) Dockerfile RUNs the scripts during build process to construct the image.

3. Docker creates image

Docker's build process reads the Dockerfile instructions including the configuration variables set by deck-build.sh and builds the image. These ARG variables are important, they form the link between deck-build and docker. Please see the Dockerfile Arguments section for more details.

Inside: Kit

The deck-build core is the library called kit. It includes bash functions and configuration files to create build plans. For additional information please see:

Inside: Dockerfile Arguments

deck-build uses Dockerfile ARG to form the link to the docker world. For example, a Dockerfile could look like that:

...
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
...
COPY ./main.sh ${DECKBUILD_PLANT}/
RUN bash ${DECKBUILD_PLANT}/main.sh
...

Building the image results in the following steps:

  • deck-build.sh gets his settings from command line arguments, configuration files, environment variables and internal calculations.
  • The resulting DECKBUILD_* values are passed as --build-arg arguments to the docker build command.
  • docker build uses these arguments to set Dockerfile's ARG values during build process.

The most important variables are:

  • ARG DECKBUILD_PLANT: The working directory inside the image is called the PLANT, deck-build passes the related folder path as ${DECKBUILD_PLANT} to the build process. Therefore a deck-build Dockerfile always includes ADD, COPY and RUN instructions that use the plant path.
  • ARG DECKBUILD_KIT: To install the kit, deck-build needs to know where it is stored. See Define The Kit for details.

Both are automatically set by deck-build but must be manually integrated into the Dockerfile (see the example above).

Inside: Plans

A plan is a bundle (directory) of a Dockerfile, one or more shell scripts and optional data files. The simplest form looks like this:

plan/             # parent directory that contains all plans
  foo/            # plan directory
    Dockerfile    # foo's Dockerfile
    main.sh       # foo's shell script

Using plans includes always two steps:

  1. Create the plan files including the Dockerfile.
  2. Use the Dockerfile to copy all plan files to the image and run plan's shell scripts inside the image during build process.

While this plan + Dockerfile commands interaction might seem a bit cumbersome, it has one big advantage: No magic! You want to use shell scripts in the build process? Then you must copy the scripts to the image at first.

Plan Artefacts

So far plan examples were simple and used the shown setup. But plans can also be large and complex. In this cases it's better to split the functionality across multiple files and optionally use subfolders to structure them. This files/subfolders are called plan artefacts and are useful cause of three reasons:

  • As always, bundling data makes the code base more clear.
  • Artefacts help to support Docker's build cache mechanism.
  • Plans and also plan artefacts are reusable in different images.

A typical use case is the system update: A lot of debian based Dockerfiles include apt-get -y update && apt-get -y upgrade followed by apt-get install <package> commands to update the image OS and install some additional software. Instead of adding this RUN commands to each Dockerfile, you can create an artefact:

cd ~/deck/plan                          # see "Installation"
mkdir foo                               # create "foo" plan ...
mkdir foo/pkg                           # create "pkg" (debian package) artefact inside of "foo"
vi foo/pkg/main.sh                      # create foo/pkg/main.sh, see below
echo "set tw=79" > foo/pkg/vimrc.local  # create additional artefact data

foo/pkg/main.sh:

#!/bin/bash
. ${DECKBUILD_KIT}/main.sh   # import kit
_debPkgs="sudo curl vim"

apt-get -y update || \
  die "Updating package repository failed"
apt-get -y --no-install-recommends upgrade || \
  die "Upgrading packages failed"
apt-get -y --no-install-recommends install ${_debPkgs} || \
  die "Installing custom packages failed"
apt-get -y autoremove || \
  die "Cleaning packages failed"
apt-get -y -f --no-install-recommends install || \
  die "Checking packages failed"

# install additional artefact data
cp ${DECKBUILD_PLANT}/pkg/vimrc.local /etc/vim/

After updating the image system, the build process should add a custom user, so we add a user artefact:

cd ~/deck/plan               # see "Installation"
vi foo/user.sh               # create foo/user.sh, see below

foo/user.sh:

#!/bin/bash
. ${DECKBUILD_KIT}/main.sh  # import kit
# installUser() and installSudoUser() are kit functions (see "Inside Kit")
installUser foo 1000    # add user: user=foo, group=foo, uid=1000, gid=1000
installSudoUser foo     # enable sudo for foo

Now, the foo plan contains the artefacts pkg (directory) and user.sh (file). To finalize our work we need to add the plan to the Dockerfile and run the build process:

cd ~/deck/plan              # see "Installation"
vi foo/Dockerfile           # create foo/Dockerfile, see below
# build image
cd ~/deck
./deck-build.sh -t deckbuild/foo:0.1.0 -p plan/foo/
docker run --rm deckbuild/foo:0.1.0

foo/Dockerfile:

FROM debian:9.5-slim

# initialization, see e.g. "Custom User example" for details
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz

# ADD PLAN
# add and run "pkg" artefact
COPY ./pkg ${DECKBUILD_PLANT}/pkg
RUN bash ${DECKBUILD_PLANT}/pkg/main.sh
# add and run "user" artefact
COPY ./user.sh ${DECKBUILD_PLANT}/
RUN bash ${DECKBUILD_PLANT}/user.sh

## print informations about foo user and exit
CMD ["id", "foo"]

Again, no magic: The build process copies the plan artefacts to the image and runs the related shell scripts.

But: The user artefact is really simple. Why we didn't add the commands to pkg/main.sh? Two reasons:

  1. Reusability: Perhaps we want to use the user artefact in other build processes too.
  2. Caching: Running the pkg update takes some time cause of the download processes. Encapsulating (COPY + RUN) this task into an artefact enforces an own image layer that will be cached during the build process. That way, adding a second user is a fast process: After adjusting user.sh docker will use the cached pkg artefact layer and rebuild the image in a few seconds.

Inside: Reuse Plans

Plans

The plan is the decisive argument when running deck-build.sh:

./deck-build.sh -t deckbuild/helloworld:0.1.0 -p plan/helloworld/

As you can see, it is only a directory that is stored on the local file system. Of course you can download this directory (from your repository) before using it:

mkdir -p ~/deck/plan
cd ~/deck/plan
curl --fail -L -O  "https://example.org/my-repo/plan/foo.tgz"
tar zxfpv foo.tgz       # should create ~/deck/plan/foo
deck-build.sh -t deckbuild/foo:0.1.0 -p ~/deck/plan/foo

But there is a better way: The deck-build -p argument also supports git repository URLs. For example you can use the deck-plan repository that contains some plans (and plan artefacts) ready to use and with additional documentation:

# use the https://github.com/flexos-io/deck-plan/tree/master/sh plan
deck-build.sh -t deckbuild/foo:0.1.0 -p github.com/flexos-io/deck-plan.git#:sh

Please see the docker build documentation for git URL details.

Plan Artefacts

As described in Inside Plans it's possible to combine multiple plan artefacts to create plans. A simple approach would be copying them to the plan directory:

# create artefact "bar"
mkdir -p ~/deck/plan/artefact/bar
# add files to bar/

# create plan "foo"
mkdir -p ~/deck/plan/foo 
cp -a ~/deck/plan/artefact/bar ~/deck/plan/foo/
# add the Dockerfile and optionally other files to foo/

# use "foo" plan to build the image
deck-build.sh -t deckbuild/foo:0.1.0 -p ~/deck/plan/foo

A more sophisticated way is downloading artefacts during build process:

Repositories:

# example.org/my-repo
plan/
  artefact/
    bar1.sh

# example.org/another-repo
plan/
  artefact/
    bar2.tgz    # contains "main.sh" and "static.txt"

Create the following Dockerfile to integrate these artefacts:

...
# get and use "bar1" artefact
ADD https://example.org/my-repo/plan/artefact/bar1.sh ${DECKBUILD_PLANT}/
RUN bash ${DECKBUILD_PLANT}/bar1.sh

# get and use "bar2" artefact
#   unfortunately ADD cannot unpack remote archives (https://github.com/moby/moby/issues/2369#issuecomment-269712515)
ADD https://example.org/another-repo/plan/artefact/bar2.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf bar2.tgz && rm bar2.tgz
RUN bash ${DECKBUILD_PLANT}/bar2/main.sh
...

The deck-plan repository contains some plan artefacts ready to use and with additional documentation.

Configuration: Environment Variables

deck-build's configuration relies heavily on environment variables exported by shell or configuration files. Configuration files himself are also defined by environment variables:

export FLEXOS_CFGS="/tmp/cfg2.sh /tmp/cfg3.sh"
export DECKBUILD_CFGS="/tmp/cfg4.sh /tmp/cfg5.sh"

With this settings the following configuration files will be processed in the following order (later have higher precedence): ${HOME}/.flexos/deck/build/cfg.sh, /tmp/cfg2.sh, /tmp/cfg3.sh, /tmp/cfg4.sh, /tmp/cfg5.sh.

Configuration files finally export DECKBUILD_* variables read by deck-build.sh. The following are currently supported:

$ cat ${HOME}/.flexos/deck/build/cfg.sh
# useful:
export DECKBUILD_USER_CFG="foo:1000:0:/home/foo:foo:1000"  # see "Abstract The Custom User"
export DECKBUILD_KIT_SRC="${HOME}/deck/build/kit"          # see "Define The Kit"
# advanced:
export DECKBUILD_ARGS=""           # see "Robust Plans"
export DECKBUILD_TMP_PLANT=false   # set to "true" to force the usage of a temporary build directory

Setting one of this variables in the current shell session overwrites values from configuration files.

Configuration: Abstract The Custom User

Taken from the Custom User example, user/main.sh will install the user foo:

...
# installUser() is a kit function (see "Inside Kit")
installUser foo 1000
...

This method is fine and works perfectly. But there are situations were you need more flexibility. For example: Staging. You develop your code and docker containers on your personal notebook with your personal user foo. However the production environment don't know the user foo, it needs the user bar instead. You could easily use different user/main.sh scripts for your local and the production environment. But there is a better solution: You can export ${DECKBUILD_USER_CFG} and use kit's setUser() function. Three simple steps are necessary:

1. Adjust plan's user/main.sh

Same for development and production:

...
# setUser() and installUser() are kit functions (see "Inside Kit")
setUser     # extracts informations from ${DECKBUILD_USER_CFG} and exports ${DECKBUILD_USER} etc.
installUser ${DECKBUILD_USER} ${DECKBUILD_USER_ID} ${DECKBUILD_USER_HOME} ${DECKBUILD_GROUP} ${DECKBUILD_GROUP_ID}
[ ${DECKBUILD_USER_SUDO} -ne 1 ] || installSudoUser ${DECKBUILD_USER}
...

Kit's setUser() function exports the following variables that you can use in plan scripts as shown above:

${DECKBUILD_USER}
${DECKBUILD_USER_ID}
${DECKBUILD_USER_SUDO}
${DECKBUILD_USER_HOME}
${DECKBUILD_GROUP}
${DECKBUILD_GROUP_ID}

2. Adjust the Dockerfile

The Dockerfile needs to consume the ${DECKBUILD_USER_CFG} variable exported by deck-build.sh in the third step:

FROM debian:9.5-slim
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz

ARG DECKBUILD_USER_CFG=
...  # see Custom User example
CMD ["id"]

3. Pass user data to build process

Development:

cd ~/deck        # see "Installation"
export DECKBUILD_USER_CFG="foo:1000:1"
./deck-build.sh -t deckbuild/user:0.2.0 -p plan/user/
docker run --rm --user foo deckbuild/user:0.2.0
# -> user=foo, group=foo, uid=1000, gid=1000, home=/home/foo, sudo enabled

Production:

cd ~/deck        # see "Installation"
export DECKBUILD_USER_CFG="bar:2000:0:/home/bar_home:bargrp:2462"
./deck-build.sh -t deckbuild/user:0.2.0 -p plan/user/
docker run --rm --user bar deckbuild/user:0.2.0
# -> user=bar, group=bargrp, uid=2000, gid=2462, home=/home/user/bar, sudo disabled

The colon separated fields of ${DECKBUILD_USER_CFG} have the following meanings: user:uid:sudoYesNo[:homePath:group:gid]

Configuration: Define The Kit

The Custom User Dockerfile downloads the kit from the deck-build repository:

...
# download and extract the kit to the image
#   unfortunately ADD cannot unpack remote archives (https://github.com/moby/moby/issues/2369#issuecomment-269712515)
ADD https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz ${DECKBUILD_PLANT}/
RUN cd ${DECKBUILD_PLANT} && tar --no-same-owner -zxpf kit.tgz && rm kit.tgz
...

The download ensures that the build process uses the specified kit release version. But there are two situations were this solution is unpractical: When your build machine doesn't have an internet connection. And when you want to use your own kit. In these situations it's possible to use a local stored kit. Only two steps are needed:

1. Adjust the Dockerfile

The Dockerfile needs to copy the local kit (provided by deck-build.sh, see step 2) to the image:

...
## begin: deck-build initialization (always needed)
ARG DECKBUILD_PLANT
ARG DECKBUILD_KIT
# use the local kit
COPY .kit ${DECKBUILD_KIT}
## end: deck-build initialization
...  # see Custom User example

2. Pass kit location to build process

cd ~/deck        # see "Installation"
export DECKBUILD_KIT_SRC=${HOME}/deck/build/kit
./deck-build.sh -t deckbuild/user:0.3.0 -p plan/user/  # copies ${DECKBUILD_KIT_SRC} to <build_context>/.kit
docker run --rm deckbuild/user:0.3.0

deck-build.sh reads the kit path from the exported ${DECKBUILD_KIT_SRC} variable and copies the kit to the docker build context.

See also: Your Own Kit

Configuration: Robust Plans

To improve the quality of your plans, you can enable the following bash settings for all your plan shell scripts:

set -o errtrace
set -o nounset
set -o errexit
set -o pipefail

1. Adjust the Dockerfile

The Dockerfile needs to consume the ${DECKBUILD_ARGS} variable exported by deck-build.sh:

...  # see Custom User example
# ${DECKBUILD_ARGS} will be parsed by kit during build process
ARG DECKBUILD_ARGS=
...

2. Pass -e flag to build process

export DECKBUILD_ARGS="-e"
./deck-build.sh ...

Customization: Your Own Kit

It's easy to create your own kit: Download and extract the kit from the deck-build repository, adjust the code and configure the new kit location:

mkdir -p ~/deck/build
cd ~/deck/build
curl --fail -L -O "https://github.com/flexos-io/deck-build/releases/download/0.1.0/kit.tgz"
tar zxfpv kit.tgz
cd kit
vi ...      # adjust code
# configure kit: see "Define The Kit"
#   set ${DECKBUILD_KIT_SRC} to ${HOME}/deck/build/kit



Powered by ExaMesh

Clone this wiki locally
You can’t perform that action at this time.