47

Tired of limited Dockerfile commands? Use full power of bash scripts instead

 5 years ago
source link: https://www.tuicool.com/articles/hit/MJza2ef
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

deck-build is a powerful but very simple and tiny bash framework 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 Usage Examples

Hello World Custom User Python

Inside

Architecture Kit Dockerfile Arguments Plans Reuse Plans

Configuration

Environment Variables Abstract The Custom User Define The Kit Robust Plans

Customization

Your Own Kit

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:

Create your plan (Dockerfile + optional shell scripts + optional data) Add kit and plan to the Dockerfile (ARG + ADD + RUN) 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 <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:

Usage examples Define the Kit Create your own kit

Kit API: List of all API functions. Please note: You don't need to use this functions, they are completely optional. Write your shell scripts as you like it.

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:

Create the plan files including the Dockerfile. 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 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:

Reusability: Perhaps we want to use the user artefact in other build processes too. 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK