├── .gitattributes ├── README.md ├── buildroot_basics.md ├── defconfigs ├── corebox_defconfig ├── nodejs_defconfig └── ruby_defconfig ├── images └── make_menuconfig_main.png ├── presentation ├── slides.odp └── slides.pdf └── scripts └── gentoo-stage3-aci.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ods diff=odf 2 | *.odt diff=odf 3 | *.odp diff=odf 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimal Containers 101 2 | 3 | ## About 4 | 5 | This presentation is a primer on the process of building minimal Linux 6 | containers. These directions should be taken with a grain of salt. 7 | 8 | As mentioned (verbally) in the presentation, these directions are not for 9 | everyone. While most folks involved with Linux will learn a thing or two about 10 | the mechanisms used for containerization, these directions are for the folks 11 | who wish to curate the content of their containers. 12 | 13 | Within this repository you'll find the original slides in Libre Office Impress 14 | "ODP" format. This is the canonical presentation. There is also a copy of the 15 | presentation rendered into PDF for those who do not have access(?) to 16 | Libre Office. 17 | 18 | This process began simply. The `busybox` image curated by Docker Inc is 19 | built using µLibC. While this may be sufficient for many users, it was 20 | insufficient for the author. This required finding a different process for 21 | building a busybox image, only using GLIBC. Enter "Buildroot". 22 | 23 | When this talk was first done (München, 2015-02-03) there was very little in 24 | the way of documentation around this process. Back in June of 2015 Docker began 25 | providing documentation around doing [this](https://github.com/docker/docker/blob/master/docs/userguide/eng-image/baseimages.md) 26 | (note: URL updated as the original file moved and seems to have been re-written). 27 | 28 | The video for the presentation can be found here: [Getting weird with containers](https://www.youtube.com/watch?v=gMpldbcMHuI) 29 | 30 | ## Basic Concept 31 | 32 | The basic principal is that you're creating a "chroot" filesystem which will be 33 | put into a "tape archive" file ([TAR](https://en.wikipedia.org/wiki/Tar_(computing))). 34 | Once the tar file is created it can be consumed by your containerization system 35 | of choice. 36 | 37 | ## Using the images 38 | 39 | ### [Docker](https://www.docker.com) 40 | 41 | Docker is a Linux containerization system written in Golang with a focus on a 42 | easy to use development experience. The proccess for importing a tar image 43 | into docker is as simple as: 44 | 45 | ``` 46 | $ cat image.tar | docker import - tagname 47 | ``` 48 | 49 | At this point the user has a complete image. While there is no metadata 50 | attached to the image, it can still be run as one would expect, simply add a 51 | command: 52 | 53 | ``` 54 | $ docker run -t -i tagname /bin/sh 55 | ``` 56 | 57 | While a user can attach a `Dockerfile` using the option `-c`, it's the opinion 58 | of the author that it's easier to follow the process in a step by step fashion. 59 | 60 | Using the previously created docker image (tagged `tagname`) we would produce 61 | the following `Dockerfile`: 62 | 63 | ``` 64 | FROM tagname 65 | 66 | CMD ["/bin/sh"] 67 | ``` 68 | 69 | After this step is performed, run the command: 70 | 71 | ``` 72 | $ docker build . tagname:v1.0 73 | ``` 74 | 75 | At this point the image is ready to be used. 76 | 77 | 78 | ### [ACI](https://github.com/appc/spec) 79 | 80 | The process for generating an ACI from a derived rootfs can be done in a number 81 | of ways. Most users will find it easiest to use the utility [`acbuild`] 82 | (https://github.com/appc/acbuild). This utility emulates the step by step 83 | nature of a Dockerfile. One issue with `acbuild` is it's heavy use of operator 84 | privileged permissions. Most users will find that they need to repeatedly 85 | "`sudo`" various commands in order to do useful work. It's important to note 86 | that this is related to `acbuild` and not ACI images in general. 87 | 88 | As an example of this there is the utility `scripts/gentoo-stage3-aci.sh` which 89 | users may use to generate an ACI image out of the current autobuild of the 90 | Gentoo stage 3 image. As this is a toolkit used for compiling operating systems 91 | it can be especially useful for automated compiles. This image is used to build 92 | base containers with Buildroot so as to have a consistent development 93 | environment. 94 | 95 | #### scripts/gentoo-stage3-aci.sh example: 96 | 97 | It should be noted that the errors coming from attempted `mknod` commands are 98 | not a problem for users running containerized workloads as these special/block 99 | files are already handled by the containerization engine. 100 | 101 | 102 | ``` 103 | $ ./gentoo-stage3-aci.sh 104 | gpg: requesting key F6CD6C97 from hkp server keys.gnupg.net 105 | gpg: key F6CD6C97: "Gentoo-keys Team " not changed 106 | gpg: Total number processed: 1 107 | gpg: unchanged: 1 108 | gpg: requesting key 2D182910 from hkp server keys.gnupg.net 109 | gpg: key 2D182910: "Gentoo Linux Release Engineering (Automated Weekly Release Key) " not changed 110 | gpg: Total number processed: 1 111 | gpg: unchanged: 1 112 | Downloading Gentoo Stage 3 (stage3-amd64-20160428.tar.bz2) 113 | % Total % Received % Xferd Average Speed Time Time Time Current 114 | Dload Upload Total Spent Left Speed 115 | 100 238M 100 238M 0 0 13.3M 0 0:00:17 0:00:17 --:--:-- 12.0M 116 | Downloading Gentoo digests 117 | % Total % Received % Xferd Average Speed Time Time Time Current 118 | Dload Upload Total Spent Left Speed 119 | 100 720 100 720 0 0 3133 0 --:--:-- --:--:-- --:--:-- 3157 120 | Downloading Gentoo digests (detached signature) 121 | % Total % Received % Xferd Average Speed Time Time Time Current 122 | Dload Upload Total Spent Left Speed 123 | 100 1588 100 1588 0 0 12107 0 --:--:-- --:--:-- --:--:-- 12215 124 | Validating GPG signatures of digest hashes 125 | gpg: Signature made Fri Apr 29 14:28:46 2016 UTC using RSA key ID 2D182910 126 | gpg: Good signature from "Gentoo Linux Release Engineering (Automated Weekly Release Key) " [unknown] 127 | gpg: WARNING: This key is not certified with a trusted signature! 128 | gpg: There is no indication that the signature belongs to the owner. 129 | Primary key fingerprint: 13EB BDBE DE7A 1277 5DFD B1BA BB57 2E0E 2D18 2910 130 | Validating SHA512 hashes from GPG signed DIGESTS file 131 | stage3-amd64-20160428.tar.bz2: OK 132 | Creating rootfs 133 | Exploding stage3 to rootfs 134 | tar: ./dev/sdd6: Cannot mknod: Operation not permitted 135 | tar: ./dev/sdc12: Cannot mknod: Operation not permitted 136 | ... 137 | ... 138 | ... 139 | tar: ./dev/sdb2: Cannot mknod: Operation not permitted 140 | tar: ./dev/tty62: Cannot mknod: Operation not permitted 141 | tar: ./dev/hda15: Cannot mknod: Operation not permitted 142 | tar: ./dev/tty42: Cannot mknod: Operation not permitted 143 | tar: ./dev/sda12: Cannot mknod: Operation not permitted 144 | tar: Exiting with failure status due to previous errors 145 | Skipping sync of portage tree. Set environment variable GENTOO_PORTAGE= to a non empty value to sync. 146 | Writing ACI manifest 147 | Building ACI image 148 | Built Image stage3-amd64-20160428.aci 149 | $ sudo rkt run --interactive --insecure-options image --dns 8.8.8.8 --volume output,kind=host,source=/home/core,readOnly=false --mount volume=output,target=/srv stage3-amd64-20160428.aci 150 | image: using image from local store for image name coreos.com/rkt/stage1-coreos:1.2.1 151 | image: using image from file stage3-amd64-20160428.aci 152 | networking: loading networks from /etc/rkt/net.d 153 | networking: loading network default with type ptp 154 | rkt-623e5da6-7f1b-4252-9acd-33d34cb0b924 / # 155 | ``` 156 | 157 | ## Relevant Links 158 | 159 | * [Buildroot](http://www.buildroot.org) - A SDK for building minimal Linux distributions like OpenWRT. 160 | * [Alpine](https://alpinelinux.org/) - A streamlined Linux distro focused on security, and lightweight footprint. Compiled using [musl libc](http://www.musl-libc.org/) vs GLIBC. Quite a bit of work around Alpine has been done by [Jeff Lindsay](https://github.com/progrium). 161 | * [debootstrap](https://wiki.debian.org/Debootstrap) - A tool to build a Debian system into a subdirectory on a Linux host. 162 | * [YUM](http://yum.baseurl.org/) / [DNF](http://dnf.baseurl.org/) - Similar principal to debootstrap. Here are a couple of examples - [Example 1](https://web.archive.org/web/20150514123601/http://prefetch.net/articles/yumchrootlinux.html) & [Example 2](https://web.archive.org/web/20141203222350/http://zaufi.github.io/administration/2014/06/10/howto-make-a-centos-chroot/) 163 | * [Gentoo](https://www.gentoo.org/downloads/) - You can directly import the Gentoo "Stage 3 Archive" image and work with it. Emerge packages, do compiles, etc. 164 | -------------------------------------------------------------------------------- /buildroot_basics.md: -------------------------------------------------------------------------------- 1 | # Building containers with Buildroot 2 | 3 | ## About 4 | 5 | As a user reading this, you've likely watched the presentation linked in the 6 | [README.md] file and thus know that the primary mechanism I use for building 7 | containers is with [Buildroot](https://buildroot.org). In truth, the way I am 8 | using Buildroot is an abuse of what it is designed for (building embedded Linux 9 | distributions). It's coincidental that all of the things I _desire_ in building 10 | a container are congruent with the design of an embedded system: 11 | 12 | - ability to choose init system (or *lack* of one in our case) 13 | - no documentation (in the container, e.g. `man`/info pages, etc) 14 | - ability to easily build without LOCALES 15 | - etc 16 | 17 | Buildroot even goes farther and let's me build everything _without a kernel_. 18 | 19 | In order to facilitate people getting started with this, I have added this 20 | getting started guide to provide a practical tutorial. 21 | 22 | In this tutorial I'm going to build a container for the server component of the 23 | task management software "[Taskwarrior](https://taskwarrior.org/)". 24 | Taskwarrior is a command line task mangement tool that embodies many of the 25 | computing theories that are near and dear to my heart, namely free/libre open 26 | source software and the "UNIX™" methodology (do one thing, do it well, and make 27 | it easy to interoperate with other tools.) 28 | 29 | These directions are not meant to be exhastive, but merely a walk through of my 30 | process for getting things done with some tips and tricks along the way. 31 | 32 | ## Getting started 33 | 34 | To get started, first one needs to identify their build environment. 35 | Personally, I use a Linux laptop (and desktop) which makes this process simple 36 | to both get started and track changes. Users on a Apple or Windows based 37 | operating system will likely benefit from using a virtual machine. Recently to 38 | facilitate this, Buildroot has begun publishing a 39 | [Vagrantfile](https://buildroot.org/downloads/Vagrantfile). 40 | 41 | We will be making a number of changes which we will want to keep in revision 42 | control. As such, we will use two git repositories. One for the upstream 43 | buildroot and one for our changes. A sample repository of (some) of my changes 44 | can be seen at https://github.com/brianredbeard/coreos_buildroot. The original 45 | source documentation on this process can be found at 46 | https://buildroot.org/downloads/manual/manual.html#outside-br-custom and will 47 | always be correct for the current stable version. The honest truth is that 48 | documentation drift is a real thing, and this document may not be up to date as 49 | the purpose is general guidance. 50 | 51 | Make a directory to hold all of our changes (`~/Projects`) and a subdirectory 52 | where we will create our git repo: 53 | 54 | ``` 55 | $ mkdir -p ~/Projects/local_buildroot 56 | ``` 57 | 58 | Perform a clone of Buildroot (`NOTE`: I do this because I often want the up 59 | to date versions of software. If you decide that you want to use one of the 60 | Buildroot stable releases you may always download it from 61 | https://buildroot.org/downloads, or simply check out the tag for the release you 62 | wish to use, e.g. `git checkout 2016.11`). 63 | 64 | ``` 65 | $ cd ~/Projects 66 | $ git clone git://git.buildroot.net/buildroot 67 | ``` 68 | 69 | Now we will create our repository to store local changes. This is just an 70 | ordinary git repository with a well structured pattern as per the "Keeping 71 | customizations outside of Buildroot" as noted above. 72 | 73 | ``` 74 | $ cd local_buildroot 75 | $ git init 76 | $ mkdir configs package 77 | ``` 78 | 79 | The true "magic" comes in when we add some metadata files which then allow 80 | Buildroot to parse/discover everything in this directory as if it were a part of 81 | the upstream tree. This requires three files at a minimum: 82 | 83 | * `external.desc` - This is a metadata file which identifies your local tree 84 | and allows for the use of multiple concurrent external trees by having 85 | unique identifiers. At a minimum you will need key called "name". 86 | * `external.mk` - This is a makefile which will include other makefiles at 87 | well defined paths, such as our packages. 88 | * `Config.in` - This is a `[Kconfig](https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt)` 89 | syntax file which provides the menuing system which we will see later when 90 | configuring our environment. 91 | 92 | In this snippet we have chosen the name "LOCAL_BUILDROOT" as the name of our 93 | external tree. This choice is at the whimsy of the operator and could have just 94 | as easily been `FOO` or `MONKEYS`. Save yourself a headache and just 95 | standardize on the uppercase version. ;) 96 | 97 | ``` 98 | $ echo 'name: LOCAL_BUILDROOT' > external.desc 99 | $ echo 'include $(sort $(wildcard $(BR2_EXTERNAL_LOCAL_BUILDROOT_PATH)/package/*/*.mk))' > external.mk 100 | $ echo 'source "$BR2_EXTERNAL_LOCAL_BUILDROOT_PATH/package/Config.in"' > Config.in 101 | ``` 102 | Through the definition of our `NAME` above, we now have a number of variables 103 | which will be exposed to us in our makefiles like 104 | `BR2_EXTERNAL_LOCAL_BUILDROOT_PATH` above. This is helpful because it means we 105 | can achieve a high degree of configuration later. 106 | 107 | At this point we now have everything defined for our local copy. Commit 108 | everything to revision control: 109 | 110 | ``` 111 | $ git add * 112 | $ git commit 113 | ``` 114 | 115 | ## Customizing our build 116 | 117 | ### Quick anecdote 118 | Let me start with an anecdote. Right after starting at CoreOS (March, 2014) all 119 | of us went on a trip to Lake Tahoe, CA, USA. We locked ourselves in a house for 120 | a week and worked on a bunch of things to make Container Linux (née CoreOS 121 | Linux) ready for a wider audience. This included the creation of 122 | `coreos-cloudinit`, changes to the `usr` partition structure, early work on 123 | GRUB, and others. A bunch of "friends of the core" joined us, including [Greg 124 | KH](https://en.wikipedia.org/wiki/Greg_Kroah-Hartman), the maintainer of the 125 | `stable` Linux kernel branch. 126 | 127 | I remember being very nervous at one point because Greg was shoulder surfing me 128 | while I was configuring a kernel (I was doing one of our early passes on the 129 | inclusion of SELinux). As I come from a background of systems administration 130 | type work, I began configuring the kernel as I assumed all gods and men before 131 | me had: using `vi`. It was at this point I hear the baritone voice of Greg 132 | behind me asking in a confused tone: 133 | 134 | "Redbeard... what are you doing?" 135 | 136 | I freeze. I have been working with Linux professionally for almost 15 years at 137 | this point. I've been compiling kernels since my days as a Slackware user in 138 | the 90s. I have obviously commited some egregious sin to have Greg stopping me. 139 | That being said, any sage wisdom is always accepted. It's at this point that I 140 | explain to him what I'm doing. He responds: 141 | 142 | "Why aren't you using the menu system?" 143 | 144 | I'm astounded. MENU system?! What kind of insult is this? I am a 145 | professional! I started using vi because when troubleshooting a system it would 146 | fit on a 3½" floppy disk, so I knew I would not have a hacked binary or 147 | corrupted editor for disaster recovery. _*I*_ do not _need_ a menu system! 148 | 149 | Greg continues, "I use the menu system all the time. It *just works* for 150 | handling dependency management and we spent a bunch of time both making sure we 151 | got it right _AND_ keeping all of the definitions up to date... Just use it." 152 | 153 | A great weight was lifted from my shoulders. There was an entire set of 154 | knowledge that could now be freed up (tracking all of this depenency management 155 | in my head) leaving more active memory for other processes. The take away here 156 | is this. Work smart, not hard. You're not going to win a pissing contest by 157 | doing extra work. Thus... we move into actually using this system... 158 | 159 | ### Configuring your build 160 | 161 | Buildroot contains a series of configuration files in Kconfig format (mentioned 162 | above). As we have a linked series of Kconfigs we can use all of the normal 163 | tools available to one for the configuration of a kernel. In our case we're 164 | going to focus on using a text user interface as it works great over SSH though 165 | you could use others as well, like `make gconfig`. 166 | 167 | To start, we will: 168 | - change back to our buildroot directory 169 | - set our `BR2_EXTERNAL` environment variable 170 | - begin the configuration process 171 | 172 | ``` 173 | $ cd ~/Projects/buildroot 174 | $ export BR2_EXTERNAL=${HOME}/Projects/local_buildroot 175 | $ make menuconfig 176 | ``` 177 | 178 | The process of setting that environment variable only needs to be done once. 179 | After it is set and a `make` process is run, it will create a new hidden file 180 | located at `~/Projects/buildroot/output/.br-external.mk`. Once this file is in 181 | place, all of the configurations found in `~/Projects/local_buildoot` should be 182 | discovered. If you decide to change your external configuration (or remove it, 183 | but keep Buildroot), just remove this file and Buildroot will no longer try to 184 | reference that path. 185 | 186 | There are other less verbose ways of doing this, but this process was chosen as 187 | it should be very clear to all Linux users that an ordinary environment variable 188 | is being used. 189 | 190 | Once `make menuconfig` is started you should see a window as follows: 191 | 192 | ![buildroot screenshot](/images/make_menuconfig_main.png) 193 | 194 | Within this menu, if `BR2_EXTERNAL` was set, you should see an option at the 195 | bottom of "`External options --->`". 196 | 197 | #### Target Options / Architecture 198 | 199 | The first step in building our container is to define the "architecture", for 200 | 99.9% of users reading this document that will be `x86_64`. The nice thing 201 | about this though is that you are not beholden to building just for x86_64. If 202 | you're one of the emerging containerization users on an ARM chipset, you can 203 | easily build the exact environment that you need. 204 | 205 | To select `x86_64` as our architecture, select "`Target Options`", then 206 | "`Architecture`". Choose `x86_64` from the list and then press "enter." 207 | 208 | When you select this, you will find the option "`Target Architecture Variant`" 209 | change to the value "`(nocona)`". Think of this as the "lowest common 210 | denominator" on the _version_ or _features_ available on the CPUs you wish to 211 | use. In general `nocona` is a safe bet, but this also means you can tune the 212 | compiling for more modern hardware and get even more performance (or features) 213 | out of your applications. For example when selecting `nocona` Buildroot will 214 | then make the following requirements on your behalf: 215 | 216 | - `BR2_X86_CPU_HAS_MMX=y` 217 | - `BR2_X86_CPU_HAS_SSE=y` 218 | - `BR2_X86_CPU_HAS_SSE2=y` 219 | - `BR2_X86_CPU_HAS_SSE3=y` 220 | 221 | While selecting `corei7` will present the following: 222 | 223 | - `BR2_X86_CPU_HAS_MMX=y` 224 | - `BR2_X86_CPU_HAS_SSE=y` 225 | - `BR2_X86_CPU_HAS_SSE2=y` 226 | - `BR2_X86_CPU_HAS_SSE3=y` 227 | - `BR2_X86_CPU_HAS_SSSE3=n` 228 | - `BR2_X86_CPU_HAS_SSE4=n` 229 | - `BR2_X86_CPU_HAS_SSE42=n` 230 | 231 | At first glance, these seem the same. Both are setting the "yes" options on the 232 | Streaming SIMD Extensions for version 1, 2, & 3. You will notice though that 233 | they also provide _additional_ options (which default to "no") when selecting 234 | `corei7`. That's because these options are never available on nocona or earlier 235 | chips, while (for example) if you have a Nahelem (or later) chipset you can add 236 | support for additional assembly instruction sets merely by toggling a flag. 237 | 238 | This is fantastic because most organizations are running relatively new 239 | hardware (Nahelem was _released_ in 2008 and _replaced_ in 2011) but "advanced" 240 | instruction sets are often not enabled in order to guarantee execution on the 241 | most number of systems possible. Think about it... now you have the capability 242 | of squeezing even more performance out of your applications, merely with 243 | configuration options. 244 | 245 | When you're done in this menu press `esc` to go back to the previous menu. 246 | 247 | #### Build options 248 | 249 | In this section, I prefer to enable the compiler cache, stack smashing protection, 250 | and having the system use relative paths. Select each of these options and hit 251 | enter. In the case of stack smashing protection, you will need to choose a type 252 | of "`-fstack-protector-strong`". 253 | 254 | #### Toolchain 255 | 256 | The toolchain section allows us to tweak what is used to build all of the 257 | components of our container/system. This means you can select things down to 258 | the level of the C library to be used, the Linux kernel application binary 259 | interface version, or even use a completely external toolchain. 260 | 261 | Change the C library to `glibc`, select your kernel headers version (note: This 262 | effectively says that you will never try to run this application on a kernel 263 | _older_ than the one you select. Thus, if you're building a container to run 264 | atop a system like CentOS 7, they will *NEVER* use a kernel newer than 3.10). 265 | In general, it's easy enough to play it safe and choose 3.4.x if you plan to run 266 | atop CentOS/RHEL 7 or a 4.x version if utilizing CoreOS). 267 | 268 | I enable C++ support, as it's required for IPv6 support. 269 | 270 | #### System configuration 271 | 272 | On the "System configuration" screen we want to first and foremost disable the 273 | init system. We're only going to run a single process, so there is no point in 274 | including things we won't use. Additionally, some users will want to change 275 | the shell that is used. By default it will be `/bin/ash`, the shell provided by 276 | Busybox. Users can also enable bash, but that can be a little bit of a dance 277 | (plus... do you _really_ need full bash? There are cases where I do, but let's 278 | not have another shellshock on our hands.) 279 | 280 | I also like to check "Purge unwanted locales" and set my list of locales to "C 281 | POSIX". Might as well uncheck "Enable root login with password" too. 282 | 283 | #### Kernel 284 | 285 | Uncheck it and move on. :) Since this is a container we already have a 286 | running kernel. 287 | 288 | #### Target packages 289 | 290 | This is where things get fun. This is where we are going to enable `taskd`, the 291 | Taskwarrior server. First though, rather than trying to find it, let's do a 292 | search, so in the window type `/` to pull up the search dialog then enter 293 | `taskd` and hit enter. 294 | 295 | Voila. We see that `taskd` can be enabled under "`Target packages`" -> 296 | "`Miscellaneous`". 297 | 298 | Go select that package, and when done hit `esc` to go to the main menu, then 299 | using the arrow keys navivate to `Save` and save the config as the name 300 | `.config`. After doing this, exit. 301 | 302 | 303 | ### Compiling your container 304 | 305 | This is the hardest part of the whole process, largely because it's the 306 | impatience of waiting. Run the following command: 307 | 308 | ``` 309 | $ make 310 | ``` 311 | 312 | That's it. You're done. When the build is done, you will see no more messages 313 | to standard out and line that says the following: 314 | 315 | `/usr/bin/install -m 0644 support/misc/target-dir-warning.txt 316 | /home/bharrington/Projects/buildroot/output/target/THIS_IS_NOT_YOUR_ROOT_FILESYSTEM` 317 | 318 | At this point, the tarball of your filesystem has been created and is located at 319 | the following path: 320 | 321 | `~/Projects/buildroot/output/images/rootfs.tar` 322 | 323 | ### Importing your container to Docker 324 | 325 | Sweet! We have a container filesystem ready to go, now let's import it into 326 | Docker and check it out: 327 | 328 | ``` 329 | $ docker import output/images/rootfs.tar taskd 330 | sha256:61b7638dc754342b3307d2c0629a7ceee5ae9a99daac6f013265aa7eb67a2a40 331 | ``` 332 | 333 | Now that it's in our local Docker repo we can try running it: 334 | 335 | ``` 336 | $ docker run -ti -u 1000:1000 taskd /bin/ash 337 | / $ 338 | ``` 339 | 340 | ### Creating a useful Dockerfile 341 | Great! We were able to get a prompt, now let's see how everything looks: 342 | 343 | ``` 344 | / $ mkdir /tmp/taskdata 345 | / $ export TASKDDATA=/tmp/taskddata 346 | / $ taskd init 347 | You must specify the 'server' variable before attempting a server start, for 348 | example: 349 | taskd config server localhost:53589 350 | 351 | Created /tmp/taskddata/config 352 | /tmp $ taskd config server localhost:53589 353 | Config file /tmp/taskddata/config modified. 354 | /tmp $ taskd config log - 355 | Config file /tmp/taskddata/config modified. 356 | ``` 357 | 358 | Seems like things are working well enough that I'm able to create a 359 | configuration. Let's try to start it: 360 | 361 | ``` 362 | /tmp $ taskd server 363 | 2017-02-06 18:44:26 ==== taskd 1.1.0 ==== 364 | 2017-02-06 18:44:26 Serving from /tmp/taskddata 365 | 2017-02-06 18:44:26 Using address localhost 366 | 2017-02-06 18:44:26 Using port 53589 367 | 2017-02-06 18:44:26 Using family 368 | 2017-02-06 18:44:26 Queue size 10 requests 369 | 2017-02-06 18:44:26 Request size limit 1048576 bytes 370 | 2017-02-06 18:44:26 IP logging on 371 | 2017-02-06 18:44:26 Certificate 372 | 2017-02-06 18:44:26 Server Certificate not readable: '' 373 | /tmp $ 374 | ``` 375 | 376 | Ok, so it looks like we'll need to note a few things to plan our container a bit 377 | further: 378 | 379 | # Where do we want to store files inside of the container? 380 | # What port do we want to listen on? 381 | # What do we do about SSL certs? 382 | 383 | Well, frankly put the first two are simple decisions the certificate management 384 | is a bit outside the scope of building a container anyways, so let's just make 385 | some decisions and keep things rolling. 386 | 387 | Let's use the values from the `Taskserver` documentation: 388 | 389 | - Port number: 53589 390 | - Data path: `/var/taskwarrior` (though I'm a little more partial to 391 | `/var/lib/taskwarrior` as it follows a little closer to the filesystem 392 | hierarchy standard - `man hier` / http://www.pathname.com/fhs/) 393 | 394 | As we've come to some decisions we can start making our Dockerfile: 395 | 396 | ``` 397 | FROM taskd 398 | 399 | # Set a default port, so we can predictable know what to publish 400 | EXPOSE 53589 401 | 402 | # Create a directory to hold our data, and make sure it's writable by the 403 | # non-root user under which we will run taskd 404 | RUN mkdir /var/taskwarrior 405 | RUN chown 1000:1000 /var/taskwarrior 406 | 407 | # Flag this location as a place we will store state 408 | VOLUME /var/taskwarrior 409 | 410 | # Set the TASKDDATA environment variable so taskd will always have it available 411 | ENV TASKDDATA=/var/taskwarrior 412 | 413 | # Specify that we should always run as UID/GID 1000 414 | USER 1000:1000 415 | 416 | # The default command to be run, any arguments to the container will always be 417 | # to this command 418 | ENTRYPOINT ["/usr/bin/taskd"] 419 | 420 | # The default argument we will use when none are provided 421 | CMD ["server"] 422 | 423 | ``` 424 | 425 | Finally, let's take this and use it: 426 | 427 | ``` 428 | $ docker build --no-cache -t taskd:v1.1.0 . 429 | Sending build context to Docker daemon 2.048 kB 430 | Step 1 : FROM taskd 431 | ---> 61b7638dc754 432 | Step 2 : EXPOSE 53589 433 | ---> Running in ef35fcc5251a 434 | ---> 246dda00cadd 435 | Removing intermediate container ef35fcc5251a 436 | Step 3 : RUN mkdir /var/taskwarrior 437 | ---> Running in 296ea1875613 438 | ---> f3a519c7018c 439 | Removing intermediate container 296ea1875613 440 | Step 4 : RUN chown 1000:1000 /var/taskwarrior 441 | ---> Running in fbfa7ce06574 442 | ---> afb38e59f206 443 | Removing intermediate container fbfa7ce06574 444 | Step 5 : VOLUME /var/taskwarrior 445 | ---> Running in e178b00bef90 446 | ---> bc0d0a572fc7 447 | Removing intermediate container e178b00bef90 448 | Step 6 : ENV TASKDDATA /var/taskwarrior 449 | ---> Running in 04d77bbc7b82 450 | ---> 017408950a47 451 | Removing intermediate container 04d77bbc7b82 452 | Step 7 : USER 1000:1000 453 | ---> Running in 3cb2053e62fd 454 | ---> 821a2a25a5b4 455 | Removing intermediate container 3cb2053e62fd 456 | Step 8 : ENTRYPOINT /usr/bin/taskd 457 | ---> Running in da869261e051 458 | ---> 74044abb1ab6 459 | Removing intermediate container da869261e051 460 | Step 9 : CMD server 461 | ---> Running in 5575a60f4ab2 462 | ---> 460efd827d14 463 | Removing intermediate container 5575a60f4ab2 464 | Successfully built 460efd827d14 465 | ``` 466 | 467 | And, let's see how we did: 468 | ``` 469 | $ docker images taskd:v1.1.0 470 | REPOSITORY TAG IMAGE ID CREATED SIZE 471 | taskd v1.1.0 460efd827d14 42 seconds ago 12.25 MB 472 | ``` 473 | 474 | Looking good at 12.25MB. 475 | 476 | ### Getting started with our container 477 | 478 | Now that we have our binaries let's try things out. First we'll need to create 479 | a place to store our data/configuration. I'm pretty partial to bind mounts, so 480 | let's create a temporary directory to use: 481 | 482 | ``` 483 | $ mktemp -d 484 | /tmp/tmp.VGjHKxakYm 485 | ``` 486 | 487 | Now, let's use that directory with taskd: 488 | 489 | ``` 490 | $ docker run -ti -v /tmp/tmp.VGjHKxakYm:/var/taskwarrior taskd:v1.1.0 init 491 | You must specify the 'server' variable before attempting a server start, for 492 | example: 493 | taskd config server localhost:53589 494 | 495 | Created /var/taskwarrior/config 496 | $ ls -l /tmp/tmp.VGjHKxakYm 497 | total 4 498 | -rw-------. 1 bharrington bharrington 187 Feb 6 11:12 config 499 | drwx------. 2 bharrington bharrington 40 Feb 6 11:12 orgs 500 | ``` 501 | 502 | Everything is continuing to look good, now lets set our various configuration 503 | options: 504 | 505 | ``` 506 | $ docker run -ti -v /tmp/tmp.VGjHKxakYm:/var/taskwarrior taskd:v1.1.0 config server localhost:53589 507 | Config file /var/taskwarrior/config modified. 508 | $ docker run -ti -v /tmp/tmp.VGjHKxakYm:/var/taskwarrior taskd:v1.1.0 config log - 509 | Config file /var/taskwarrior/config modified. 510 | $ cat /tmp/tmp.VGjHKxakYm/config 511 | confirmation=1 512 | extensions=/usr/libexec/taskd 513 | ip.log=on 514 | log=- 515 | pid.file=/tmp/taskd.pid 516 | queue.size=10 517 | request.limit=1048576 518 | root=/var/taskwarrior 519 | server=localhost:53589 520 | trust=strict 521 | verbose=1 522 | 523 | ``` 524 | 525 | As you can see, if we had an existing set of configurations we would be able to 526 | just drop them in and go. 527 | 528 | # vim: set ts=2 sw=2 expandtab textwidth=80: 529 | -------------------------------------------------------------------------------- /defconfigs/corebox_defconfig: -------------------------------------------------------------------------------- 1 | BR2_x86_64=y 2 | BR2_TOOLCHAIN_BUILDROOT_GLIBC=y 3 | BR2_TOOLCHAIN_BUILDROOT_CXX=y 4 | BR2_ENABLE_LOCALE_PURGE=y 5 | BR2_ENABLE_LOCALE_WHITELIST="C POSIX" 6 | BR2_TARGET_GENERIC_HOSTNAME="corebox" 7 | BR2_TARGET_GENERIC_ISSUE="Welcome to CoreBox" 8 | BR2_TARGET_GENERIC_PASSWD_SHA512=y 9 | BR2_INIT_NONE=y 10 | BR2_SYSTEM_BIN_SH_BASH=y 11 | # BR2_TARGET_GENERIC_GETTY is not set 12 | # BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set 13 | BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y 14 | BR2_PACKAGE_COREUTILS=y 15 | -------------------------------------------------------------------------------- /defconfigs/nodejs_defconfig: -------------------------------------------------------------------------------- 1 | BR2_x86_64=y 2 | BR2_TOOLCHAIN_BUILDROOT_GLIBC=y 3 | BR2_TOOLCHAIN_BUILDROOT_CXX=y 4 | BR2_ENABLE_LOCALE_PURGE=y 5 | BR2_ENABLE_LOCALE_WHITELIST="C POSIX" 6 | BR2_TARGET_GENERIC_HOSTNAME="nodejs-base" 7 | BR2_TARGET_GENERIC_ISSUE="NodeJS Base" 8 | BR2_TARGET_GENERIC_PASSWD_SHA512=y 9 | BR2_INIT_NONE=y 10 | BR2_SYSTEM_BIN_SH_BASH=y 11 | # BR2_TARGET_GENERIC_GETTY is not set 12 | # BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set 13 | BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y 14 | BR2_PACKAGE_COREUTILS=y 15 | BR2_GCC_ENABLE_OPENMP=y 16 | BR2_PACKAGE_NODEJS=y 17 | BR2_PACKAGE_NODEJS_NPM=y 18 | -------------------------------------------------------------------------------- /defconfigs/ruby_defconfig: -------------------------------------------------------------------------------- 1 | BR2_x86_64=y 2 | BR2_TOOLCHAIN_BUILDROOT_GLIBC=y 3 | BR2_TOOLCHAIN_BUILDROOT_CXX=y 4 | BR2_ENABLE_LOCALE_PURGE=y 5 | BR2_ENABLE_LOCALE_WHITELIST="C POSIX" 6 | BR2_TARGET_GENERIC_HOSTNAME="ruby-base" 7 | BR2_TARGET_GENERIC_ISSUE="Ruby Base" 8 | BR2_TARGET_GENERIC_PASSWD_SHA512=y 9 | BR2_INIT_NONE=y 10 | BR2_SYSTEM_BIN_SH_BASH=y 11 | # BR2_TARGET_GENERIC_GETTY is not set 12 | # BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set 13 | BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y 14 | BR2_PACKAGE_COREUTILS=y 15 | BR2_PACKAGE_RUBY=y 16 | -------------------------------------------------------------------------------- /images/make_menuconfig_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianredbeard/minimal_containers/f3d610d8b4228c25a6737d20b1af882000ade4bd/images/make_menuconfig_main.png -------------------------------------------------------------------------------- /presentation/slides.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianredbeard/minimal_containers/f3d610d8b4228c25a6737d20b1af882000ade4bd/presentation/slides.odp -------------------------------------------------------------------------------- /presentation/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianredbeard/minimal_containers/f3d610d8b4228c25a6737d20b1af882000ade4bd/presentation/slides.pdf -------------------------------------------------------------------------------- /scripts/gentoo-stage3-aci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Set default values 5 | : ${GENTOO_ARCH:=amd64} 6 | : ${GENTOO_PROFILE:=""} 7 | : ${GENTOO_PORTAGE:=""} 8 | : ${GENTOO_MIRROR:="http://distfiles.gentoo.org"} 9 | 10 | if [ "$( echo ${1} | tr a-z A-Z)" == "HELP" ]; then 11 | echo " This utility can be configured with a number of options including 12 | \$GENTOO_ARCH (defaults to amd64) 13 | \$GENTOO_PROFILE (e.g. nomultilib, hardened, minimal. defaults to nothing) 14 | \$GENTOO_PORTAGE (include portage? defaults to no) 15 | \$GENTOO_MIRROR (use alternate mirror. defaults to http://distfiles.gentoo.org)" 16 | fi 17 | 18 | # Check for Gentoo profile, if there if a profile, add a "-" 19 | if [ "${GENTOO_PROFILE}x" != "x" ]; then 20 | GENTOO_PROFILE="-${GENTOO_PROFILE}" 21 | fi 22 | 23 | # Import the Gentoo signing keys 24 | 25 | # Gentoo-keys team 26 | gpg --recv-key --keyserver hkp://keys.gnupg.net '0x825533CBF6CD6C97' 27 | 28 | # Gentoo automated weekly release key 29 | gpg --recv-key --keyserver hkp://keys.gnupg.net '0xBB572E0E2D182910' 30 | 31 | 32 | # Identify the current Gentoo version 33 | GENTOO_CUR=`curl -s ${GENTOO_MIRROR}/releases/${GENTOO_ARCH}/autobuilds/latest-stage3-${GENTOO_ARCH}${GENTOO_PROFILE}.txt | awk '/stage3/ {print $1}'` 34 | 35 | GENTOO_BZIP=${GENTOO_CUR##*/} 36 | GENTOO_TAR=${GENTOO_BZIP%%.bz2} 37 | 38 | : ${ACI_NAME:=${GENTOO_TAR%%.tar}} 39 | 40 | # Pull down the Gentoo stage3 image 41 | 42 | if [ ! -e ${GENTOO_BZIP} ]; then 43 | echo "Downloading Gentoo Stage 3 (${GENTOO_BZIP})" 44 | curl -O ${GENTOO_MIRROR}/releases/${GENTOO_ARCH}/autobuilds/current-stage3-${GENTOO_ARCH}${GENTOO_PROFILE}/${GENTOO_BZIP} 45 | echo "Downloading Gentoo digests" 46 | curl -O ${GENTOO_MIRROR}/releases/${GENTOO_ARCH}/autobuilds/current-stage3-${GENTOO_ARCH}${GENTOO_PROFILE}/${GENTOO_BZIP}.DIGESTS 47 | echo "Downloading Gentoo digests (detached signature)" 48 | curl -O ${GENTOO_MIRROR}/releases/${GENTOO_ARCH}/autobuilds/current-stage3-${GENTOO_ARCH}${GENTOO_PROFILE}/${GENTOO_BZIP}.DIGESTS.asc 49 | fi 50 | 51 | # Temporarily change the exit behavior to provide more accurate error messages 52 | set +e 53 | # Validate GPG hashes 54 | echo "Validating GPG signatures of digest hashes" 55 | gpg --verify ${GENTOO_BZIP}.DIGESTS.asc 56 | EXIT_CODE=$? 57 | if [ "${EXIT_CODE}" != "0" ]; then 58 | echo "Digest file failed GPG validation (Exit code: ${EXIT_CODE})." 59 | exit ${EXIT_CODE} 60 | fi 61 | 62 | # Check to ensure that the images were signed with the proper release key 63 | echo "Validating SHA512 hashes from GPG signed DIGESTS file" 64 | grep -A1 SHA512 ${GENTOO_BZIP}.DIGESTS.asc | awk "/${GENTOO_BZIP}$/ {print \$0}" | sha512sum -c - 65 | EXIT_CODE=$? 66 | if [ "${EXIT_CODE}" != "0" ]; then 67 | echo "Payload file failed SHA512 validation (Exit code: ${EXIT_CODE})." 68 | exit ${EXIT_CODE} 69 | fi 70 | 71 | set -e 72 | # If the rootfs does not exist, then explode the corresponding tarball 73 | # and install the Gentoo portage tree into the correct location 74 | 75 | if [ ! -d "rootfs" ]; then 76 | 77 | echo "Creating rootfs" 78 | mkdir -p rootfs 79 | 80 | set +e 81 | echo "Exploding stage3 to rootfs" 82 | tar jxpf ${GENTOO_BZIP} -C rootfs --xattrs 83 | set -e 84 | mkdir -p rootfs/usr/portage 85 | if [ "${GENTOO_PORTAGE}x" != "x" ]; then 86 | echo "Performing rsync of portage tree" 87 | rsync -az rsync://rsync.us.gentoo.org/gentoo-portage rootfs/usr/portage/ 88 | echo "Completed rsync of portage tree" 89 | else 90 | echo "Skipping sync of portage tree. Set environment variable GENTOO_PORTAGE= to a non empty value to sync." 91 | fi 92 | else 93 | echo "Directory rootfs already exists. Content may be out of sync." 94 | fi 95 | 96 | # Begin operations to finish packaging our assets into ACI format 97 | echo "Writing ACI manifest" 98 | echo "{\"acKind\":\"ImageManifest\",\"acVersion\":\"0.7.4\",\"name\":\"${ACI_NAME}\",\"labels\":[{\"name\":\"os\",\"value\":\"linux\"},{\"name\":\"arch\",\"value\":\"${GENTOO_ARCH}\"}],\"app\":{\"exec\":[\"/bin/bash\"],\"environment\":[{\"name\":\"TERM\",\"value\":\"linux\"},{\"name\":\"LANG\",\"value\":\"en_US.UTF-8\"}],\"user\":\"0\",\"group\":\"0\"}}" > manifest 99 | 100 | echo "Building ACI image" 101 | tar Jcpf ${GENTOO_TAR%%.tar}.aci --xattrs rootfs manifest 102 | 103 | echo "Built Image ${GENTOO_TAR%%.tar}.aci" 104 | 105 | 106 | # some common packages needed to build subsequent packages include: 107 | # dev-vcs/git 108 | # sys-devel/bc 109 | # app-arch/cpio 110 | --------------------------------------------------------------------------------