diff --git a/docs/user_guide/build_code.md b/docs/user_guide/build_code.md new file mode 100644 index 0000000..6c49e5b --- /dev/null +++ b/docs/user_guide/build_code.md @@ -0,0 +1,93 @@ +A common first step before building a container image is to build your code in a +consistent environment. Stacker provides a hermetically sealed environment in a +build-only container to produce the binaries and other artifacts required for +the container image. Whether you are building a container image or not, it is a +good idea to use stacker's container-based builds to produce your binaries, +especially when you need a consistent build environment for all the developers +collaborating on the same project. + +## Go Code Example + +As an example, let's build a simple go file using stacker: + +```bash title="Go build using stacker" +cat > "hello_stacker.go" << EOF +package main + +import "fmt" + +func main() { + fmt.Println("Hello Stacker!") +} +EOF + +cat > "go_build.stacker.yaml" << EOF +go-build-hello-stacker: + from: + type: docker + url: docker://zothub.io/c3/ubuntu/go-devel-amd64:1.19.2 + import: + - ./hello_stacker.go + build_only: true + run: | + # Source Go toolchain env + . /etc/profile + + # Setup Go ENV + mkdir -p /go + export GOPATH=/go + mkdir -p /go/src + cd /go/src + + # Copy source code to go path + cp /stacker/hello_stacker.go . + + # Build code + go build -o hello_stacker hello_stacker.go + + # Test code + ./hello_stacker +EOF + +stacker build -f go_build.stacker.yaml +preparing image hello-go-stacker... +using cached copy of /dev/shm/ravi/hello_stacker.go +loading docker://zothub.io/c3/ubuntu/go-devel-amd64:1.19.2 +Copying blob b900f44d647a skipped: already exists +Copying config 3ece5b544e done +Writing manifest to image destination +Storing signatures +cache miss because layer definition was changed ++ . /etc/profile ++ export 'HOME=/go' ++ export 'GOROOT=/opt/go' ++ export 'PATH=/opt/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' ++ mkdir -p /tmp/go/cache ++ export 'GOCACHE=/tmp/go/cache' ++ mkdir -p /go ++ export 'GOPATH=/go' ++ mkdir -p /go/src ++ cd /go/src ++ cp /stacker/hello_stacker.go . ++ go build -o hello_stacker hello_stacker.go ++ ./hello_stacker +Hello Stacker! +``` + +The above script creates a go file called `hello_stacker.go`, then uses a go +development container from `zothub.io/c3/ubuntu/go-devel-amd:1.19.2` to build +`hello_stacker.go`. + +Let's look at details of the stacker file sections used: + +| Stacker file Keyword | Description | +| -------------------- | ----------------------------------------------------- | +| `from` | specifies the base container to be used for this build| +| `import` | import file from host into the build container | +| `build_only` | mark this as a build container to avoid layer creation| +| `run` | build script to execute | + +Note `cp /stacker/hello_stacker.go .` in the `run` script. Stacker bind mounts +all the imported files under a special read-only directory `/stacker`. This +directory can be used in the `run` script to access the host mounted files in +the build script. diff --git a/docs/user_guide/build_container_image.md b/docs/user_guide/build_container_image.md index e69de29..f79ce13 100644 --- a/docs/user_guide/build_container_image.md +++ b/docs/user_guide/build_container_image.md @@ -0,0 +1,171 @@ +# Image Build + +Stacker is a tool that allows for building OCI images in a reproducible manner, +completely unprivileged. For this tutorial, we assume you have followed the +[Get Stacker](../get_started/get_stacker.md) guide. + +### First `stacker.yaml` + +The basic input to stacker is the `stacker.yaml` file, which describes what the +base for your OCI image should be, and what to do to construct it. One of the +smallest stacker files is just: + + first: + from: + type: docker + url: docker://centos:latest + +Note the key `first` represents the name of the layer, and it can have any value +except `config`, which has a special usage, see the +[stacker file](../reference/stacker_file.md) documentation. + +With this stacker file as `first.yaml`, we can do a basic stacker build: + + $ stacker build -f first.yaml + building image first... + importing files... + Getting image source signatures + Copying blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 + 69.60 MB / 69.60 MB [=====================================================] 16s + Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e + 862 B / 862 B [============================================================] 0s + Writing manifest to image destination + Storing signatures + unpacking to /home/ubuntu/tutorial/roots/_working + running commands... + generating layer... + filesystem first built successfully + +What happened here is that stacker downloaded the `centos:latest` tag from the +docker hub and generated it as an OCI image with tag "first". We can verify +this: + + $ umoci ls --layout oci + centos-latest + first + +The centos-latest there is the OCI tag for the base image, and first is the +image we generated. + +The next thing to note is that if we do another rebuild, less things happen: + + $ stacker build -f first.yaml + building image first... + importing files... + found cached layer first + +Stacker will cache all of the inputs to stacker files, and only rebuild when +one of them changes. The cache (and all of stacker's metadata) live in the `.stacker` directory where you run stacker from. Stacker's metadata can be cleaned with `stacker clean`, and its entire cache can be removed with `stacker clean`. + +So far, the only input is a base image, but what about if we want to import a +script to run or a config file? Consider the next example: + + first: + from: + type: docker + url: docker://centos:latest + import: + - config.json + - install.sh + run: | + mkdir -p /etc/myapp + cp /stacker/config.json /etc/myapp/ + /stacker/install.sh + +If the content of `install.sh` is just `echo hello world`, then stacker's +output will look something like: + + $ stacker build -f first.yaml + building image first... + importing files... + copying config.json + copying install.sh + Getting image source signatures + Skipping fetch of repeat blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 + Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e + 862 B / 862 B [============================================================] 0s + Writing manifest to image destination + Storing signatures + unpacking to /home/ubuntu/tutorial/roots/_working + running commands... + running commands for first + + mkdir -p /etc/myapp + + cp /stacker/config.json /etc/myapp + + /stacker/install.sh + hello world + generating layer... + filesystem first built successfully + +There are two new stacker file directives here: + + import: + - config.json + - install.sh + +Which imports those two files into the `/stacker` directory inside the image. +This directory will not be present during the final image, so copy any files +you need out of it into their final place in the image. Also, importing things +from the web (via http://example.com/foo.tar.gz urls) is supported, and these +things will be cached on disk. Stacker will not evaluate as long as it has a +file there, so if something at the URL changes, you need to run `stacker build` +with the `--no-cache` argument, or simply delete the file from +`.stacker/imports/$target_name/foo.tar.gz`. + +And then there is: + + run: | + mkdir -p /etc/myapp + cp /stacker/config.json /etc/myapp/ + /stacker/install.sh + +Which is the set of commands to run in order to install and configure the +image. + +Also note that it used a cached version of the base layer, but then re-built +the part where you asked for commands to be run, since that is new. + +### dev/build containers + +Finally, stacker offers "build only" containers, which are just built, but not +emitted in the final OCI image. For example: + + build: + from: + type: docker + url: docker://ubuntu:latest + run: | + apt update + apt install -y software-properties-common git + apt-add-repository -y ppa:gophers/archive + apt update + apt install -y golang-1.9 + export PATH=$PATH:/usr/lib/go-1.9/bin + export GOPATH=~/go + mkdir -p $GOPATH/src/github.com/openSUSE + cd $GOPATH/src/github.com/openSUSE + git clone https://github.com/opencontainers/umoci + cd umoci + make umoci.static + cp umoci.static / + build_only: true + umoci: + from: + type: docker + url: docker://centos:latest + import: stacker://build/umoci.static + run: cp /stacker/umoci.static /usr/bin/umoci + +Will build a static version of umoci in an ubuntu container, but the final +image will only contain an `umoci` tag with a statically linked version of +`umoci` at `/usr/bin/umoci`. There are a few new directives to support this: + + build_only: true + +indicates that the container shouldn't be emitted in the final image, because +we're going to import something from it and don't need the rest of it. The +line: + + import: stacker://build/umoci.static + +is what actually does this import, and it says "from a previously built stacker +image called 'build', import /umoci.static". diff --git a/docs/user_guide/container_build.md b/docs/user_guide/container_build.md deleted file mode 100644 index f79ce13..0000000 --- a/docs/user_guide/container_build.md +++ /dev/null @@ -1,171 +0,0 @@ -# Image Build - -Stacker is a tool that allows for building OCI images in a reproducible manner, -completely unprivileged. For this tutorial, we assume you have followed the -[Get Stacker](../get_started/get_stacker.md) guide. - -### First `stacker.yaml` - -The basic input to stacker is the `stacker.yaml` file, which describes what the -base for your OCI image should be, and what to do to construct it. One of the -smallest stacker files is just: - - first: - from: - type: docker - url: docker://centos:latest - -Note the key `first` represents the name of the layer, and it can have any value -except `config`, which has a special usage, see the -[stacker file](../reference/stacker_file.md) documentation. - -With this stacker file as `first.yaml`, we can do a basic stacker build: - - $ stacker build -f first.yaml - building image first... - importing files... - Getting image source signatures - Copying blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 - 69.60 MB / 69.60 MB [=====================================================] 16s - Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e - 862 B / 862 B [============================================================] 0s - Writing manifest to image destination - Storing signatures - unpacking to /home/ubuntu/tutorial/roots/_working - running commands... - generating layer... - filesystem first built successfully - -What happened here is that stacker downloaded the `centos:latest` tag from the -docker hub and generated it as an OCI image with tag "first". We can verify -this: - - $ umoci ls --layout oci - centos-latest - first - -The centos-latest there is the OCI tag for the base image, and first is the -image we generated. - -The next thing to note is that if we do another rebuild, less things happen: - - $ stacker build -f first.yaml - building image first... - importing files... - found cached layer first - -Stacker will cache all of the inputs to stacker files, and only rebuild when -one of them changes. The cache (and all of stacker's metadata) live in the `.stacker` directory where you run stacker from. Stacker's metadata can be cleaned with `stacker clean`, and its entire cache can be removed with `stacker clean`. - -So far, the only input is a base image, but what about if we want to import a -script to run or a config file? Consider the next example: - - first: - from: - type: docker - url: docker://centos:latest - import: - - config.json - - install.sh - run: | - mkdir -p /etc/myapp - cp /stacker/config.json /etc/myapp/ - /stacker/install.sh - -If the content of `install.sh` is just `echo hello world`, then stacker's -output will look something like: - - $ stacker build -f first.yaml - building image first... - importing files... - copying config.json - copying install.sh - Getting image source signatures - Skipping fetch of repeat blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 - Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e - 862 B / 862 B [============================================================] 0s - Writing manifest to image destination - Storing signatures - unpacking to /home/ubuntu/tutorial/roots/_working - running commands... - running commands for first - + mkdir -p /etc/myapp - + cp /stacker/config.json /etc/myapp - + /stacker/install.sh - hello world - generating layer... - filesystem first built successfully - -There are two new stacker file directives here: - - import: - - config.json - - install.sh - -Which imports those two files into the `/stacker` directory inside the image. -This directory will not be present during the final image, so copy any files -you need out of it into their final place in the image. Also, importing things -from the web (via http://example.com/foo.tar.gz urls) is supported, and these -things will be cached on disk. Stacker will not evaluate as long as it has a -file there, so if something at the URL changes, you need to run `stacker build` -with the `--no-cache` argument, or simply delete the file from -`.stacker/imports/$target_name/foo.tar.gz`. - -And then there is: - - run: | - mkdir -p /etc/myapp - cp /stacker/config.json /etc/myapp/ - /stacker/install.sh - -Which is the set of commands to run in order to install and configure the -image. - -Also note that it used a cached version of the base layer, but then re-built -the part where you asked for commands to be run, since that is new. - -### dev/build containers - -Finally, stacker offers "build only" containers, which are just built, but not -emitted in the final OCI image. For example: - - build: - from: - type: docker - url: docker://ubuntu:latest - run: | - apt update - apt install -y software-properties-common git - apt-add-repository -y ppa:gophers/archive - apt update - apt install -y golang-1.9 - export PATH=$PATH:/usr/lib/go-1.9/bin - export GOPATH=~/go - mkdir -p $GOPATH/src/github.com/openSUSE - cd $GOPATH/src/github.com/openSUSE - git clone https://github.com/opencontainers/umoci - cd umoci - make umoci.static - cp umoci.static / - build_only: true - umoci: - from: - type: docker - url: docker://centos:latest - import: stacker://build/umoci.static - run: cp /stacker/umoci.static /usr/bin/umoci - -Will build a static version of umoci in an ubuntu container, but the final -image will only contain an `umoci` tag with a statically linked version of -`umoci` at `/usr/bin/umoci`. There are a few new directives to support this: - - build_only: true - -indicates that the container shouldn't be emitted in the final image, because -we're going to import something from it and don't need the rest of it. The -line: - - import: stacker://build/umoci.static - -is what actually does this import, and it says "from a previously built stacker -image called 'build', import /umoci.static". diff --git a/mkdocs.yml b/mkdocs.yml index c716b44..08e2d6e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,6 +61,7 @@ markdown_extensions: - def_list - footnotes - md_in_html + - tables - toc: permalink: true - pymdownx.superfences @@ -96,7 +97,7 @@ nav: - Stacker Architecture: concepts/stacker_architecture.md - User Guide: - Build Environment: user_guide/build_environment.md - - Container Build: user_guide/container_build.md + - Build Code: user_guide/build_code.md - Build Container Image: user_guide/build_container_image.md - Scratch Image: user_guide/scratch_image.md - Template Substitution: user_guide/template_substitution.md