2021-08-10 08:24:15 +00:00
{ bashInteractive
, buildPackages
, cacert
, callPackage
, closureInfo
, coreutils
2024-06-29 15:07:17 +00:00
, devShellTools
2021-08-10 08:24:15 +00:00
, e2fsprogs
2023-08-16 15:50:10 +00:00
, proot
2022-03-24 17:47:30 +00:00
, fakeNss
2021-08-10 08:24:15 +00:00
, fakeroot
2024-02-17 16:48:57 +00:00
, file
2021-08-10 08:24:15 +00:00
, go
, jq
, jshon
, lib
, makeWrapper
, moreutils
, nix
2022-04-05 00:18:45 +00:00
, nixosTests
2021-08-10 08:24:15 +00:00
, pigz
, rsync
, runCommand
, runtimeShell
, shadow
, skopeo
2024-07-17 08:06:42 +00:00
, stdenv
2021-08-10 08:24:15 +00:00
, storeDir ? builtins . storeDir
, substituteAll
, symlinkJoin
2022-04-05 00:18:45 +00:00
, tarsum
2021-08-10 08:24:15 +00:00
, util-linux
, vmTools
2024-03-15 00:38:19 +00:00
, writeClosure
2021-08-10 08:24:15 +00:00
, writeScript
2022-05-09 18:43:52 +00:00
, writeShellScriptBin
2021-08-10 08:24:15 +00:00
, writeText
, writeTextDir
, writePython3
2024-02-17 16:48:57 +00:00
, zstd
2016-09-27 23:42:05 +00:00
} :
2015-11-19 12:11:17 +00:00
2020-05-02 14:30:43 +00:00
let
2021-10-01 13:53:30 +00:00
inherit ( lib )
optionals
optionalString
;
2020-05-02 14:30:43 +00:00
2021-10-18 10:39:51 +00:00
inherit ( lib )
escapeShellArgs
toList
;
2024-06-29 15:07:17 +00:00
inherit ( devShellTools )
2024-06-29 15:22:57 +00:00
valueToString
2024-06-29 15:07:17 +00:00
;
2021-08-10 08:24:15 +00:00
mkDbExtraCommand = contents :
let
contentsList = if builtins . isList contents then contents else [ contents ] ;
in
''
echo " G e n e r a t i n g t h e n i x d a t a b a s e . . . "
echo " W a r n i n g : o n l y t h e d a t a b a s e o f t h e d e e p e s t N i x l a y e r i s l o a d e d . "
echo " I f y o u w a n t t o u s e n i x c o m m a n d s i n t h e c o n t a i n e r , i t w o u l d "
echo " b e b e t t e r t o o n l y h a v e o n e l a y e r t h a t c o n t a i n s a n i x s t o r e . "
export NIX_REMOTE = local ? root = $ PWD
# A user is required by nix
# https://github.com/NixOS/nix/blob/9348f9291e5d9e4ba3c4347ea1b235640f54fd79/src/libutil/util.cc#L478
export USER = nobody
$ { buildPackages . nix } /bin/nix-store - - load-db < $ { closureInfo { rootPaths = contentsList ; } } /registration
2024-02-18 20:15:26 +00:00
# Reset registration times to make the image reproducible
$ { buildPackages . sqlite } /bin/sqlite3 nix/var/nix/db/db.sqlite " U P D A T E V a l i d P a t h s S E T r e g i s t r a t i o n T i m e = ' ' ${ SOURCE_DATE_EPOCH } "
2021-08-10 08:24:15 +00:00
mkdir - p nix/var/nix/gcroots/docker /
for i in $ { lib . concatStringsSep " " contentsList } ; do
ln - s $ i nix/var/nix/gcroots/docker / $ ( basename $ i )
done ;
'' ;
2016-09-27 23:42:05 +00:00
2020-12-12 23:42:31 +00:00
# The OCI Image specification recommends that configurations use values listed
# in the Go Language document for GOARCH.
2020-12-12 02:54:44 +00:00
# Reference: https://github.com/opencontainers/image-spec/blob/master/config.md#properties
2020-12-12 23:42:31 +00:00
# For the mapping from Nixpkgs system parameters to GOARCH, we can reuse the
# mapping from the go package.
2022-12-07 13:07:47 +00:00
defaultArchitecture = go . GOARCH ;
2020-11-19 14:03:44 +00:00
2024-02-17 16:48:57 +00:00
compressors = {
none = {
ext = " " ;
nativeInputs = [ ] ;
compress = " c a t " ;
decompress = " c a t " ;
} ;
gz = {
ext = " . g z " ;
nativeInputs = [ pigz ] ;
compress = " p i g z - p $ N I X _ B U I L D _ C O R E S - n T R " ;
decompress = " p i g z - d - p $ N I X _ B U I L D _ C O R E S " ;
} ;
zstd = {
ext = " . z s t " ;
nativeInputs = [ zstd ] ;
compress = " z s t d - T $ N I X _ B U I L D _ C O R E S " ;
decompress = " z s t d - d - T $ N I X _ B U I L D _ C O R E S " ;
} ;
} ;
compressorForImage = compressor : imageName : compressors . ${ compressor } or
( throw " i n d o c k e r i m a g e ${ imageName } : c o m p r e s s o r m u s t b e o n e o f : [ ${ toString builtins . attrNames compressors } ] " ) ;
2020-05-02 14:30:43 +00:00
in
2015-11-19 12:11:17 +00:00
rec {
2019-06-27 18:15:42 +00:00
examples = callPackage ./examples.nix {
2022-05-16 15:00:54 +00:00
inherit buildImage buildLayeredImage fakeNss pullImage shadowSetup buildImageWithNixDb streamNixShellImage ;
2016-10-03 17:07:33 +00:00
} ;
2022-04-05 00:18:45 +00:00
tests = {
inherit ( nixosTests )
docker-tools
docker-tools-overlay
# requires remote builder
# docker-tools-cross
;
} ;
2021-08-10 08:24:15 +00:00
pullImage =
let
fixName = name : builtins . replaceStrings [ " / " " : " ] [ " - " " - " ] name ;
in
2018-05-06 02:38:47 +00:00
{ imageName
2018-04-03 08:26:03 +00:00
# To find the digest of an image, you can use skopeo:
2018-06-13 00:56:13 +00:00
# see doc/functions.xml
2018-05-06 02:38:47 +00:00
, imageDigest
, sha256
2018-06-13 00:56:13 +00:00
, os ? " l i n u x "
2022-11-21 12:10:07 +00:00
, # Image architecture, defaults to the architecture of the `hostPlatform` when unset
2022-12-07 13:07:47 +00:00
arch ? defaultArchitecture
2019-03-26 09:35:21 +00:00
# This is used to set name to the pulled image
, finalImageName ? imageName
2018-04-03 08:26:03 +00:00
# This used to set a tag to the pulled image
2018-05-06 02:38:47 +00:00
, finalImageTag ? " l a t e s t "
2021-05-05 08:40:08 +00:00
# This is used to disable TLS certificate verification, allowing access to http registries on (hopefully) trusted networks
, tlsVerify ? true
2019-03-26 09:35:21 +00:00
, name ? fixName " d o c k e r - i m a g e - ${ finalImageName } - ${ finalImageTag } . t a r "
2018-05-06 02:38:47 +00:00
} :
2021-08-10 08:24:15 +00:00
runCommand name
{
inherit imageDigest ;
imageName = finalImageName ;
imageTag = finalImageTag ;
impureEnvVars = lib . fetchers . proxyImpureEnvVars ;
outputHashMode = " f l a t " ;
outputHashAlgo = " s h a 2 5 6 " ;
outputHash = sha256 ;
2022-04-05 00:18:45 +00:00
nativeBuildInputs = [ skopeo ] ;
2021-08-10 08:24:15 +00:00
SSL_CERT_FILE = " ${ cacert . out } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
sourceURL = " d o c k e r : / / ${ imageName } @ ${ imageDigest } " ;
destNameTag = " ${ finalImageName } : ${ finalImageTag } " ;
} ''
2021-05-05 08:40:08 +00:00
skopeo \
- - insecure-policy \
- - tmpdir = $ TMPDIR \
- - override-os $ { os } \
- - override-arch $ { arch } \
2021-08-14 21:21:26 +00:00
copy \
- - src-tls-verify = $ { lib . boolToString tlsVerify } \
" $ s o u r c e U R L " " d o c k e r - a r c h i v e : / / $ o u t : $ d e s t N a m e T a g " \
2021-07-03 01:38:07 +00:00
| cat # pipe through cat to force-disable progress bar
2018-05-06 02:38:47 +00:00
'' ;
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
# We need to sum layer.tar, not a directory, hence tarsum instead of nix-hash.
2021-01-15 13:22:34 +00:00
# And we cannot untar it, because then we cannot preserve permissions etc.
2022-04-05 00:18:45 +00:00
inherit tarsum ; # pkgs.dockerTools.tarsum
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
# buildEnv creates symlinks to dirs, which is hard to edit inside the overlay VM
2021-08-10 08:24:15 +00:00
mergeDrvs =
{ derivations
, onlyDeps ? false
} :
runCommand " m e r g e - d r v s "
{
inherit derivations onlyDeps ;
} ''
2016-09-27 23:42:05 +00:00
if [ [ - n " $ o n l y D e p s " ] ] ; then
echo $ derivations > $ out
2015-11-19 12:11:17 +00:00
exit 0
fi
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
mkdir $ out
2016-09-27 23:42:05 +00:00
for derivation in $ derivations ; do
echo " M e r g i n g $ d e r i v a t i o n . . . "
if [ [ - d " $ d e r i v a t i o n " ] ] ; then
# If it's a directory, copy all of its contents into $out.
cp - drf - - preserve = mode - f $ derivation /* $ o u t /
2015-11-19 12:11:17 +00:00
else
2016-09-27 23:42:05 +00:00
# Otherwise treat the derivation as a tarball and extract it
# into $out.
2015-11-19 12:11:17 +00:00
tar - C $ out - xpf $ drv || true
fi
done
'' ;
2016-09-27 23:42:05 +00:00
# Helper for setting up the base files for managing users and
# groups, only if such files don't exist already. It is suitable for
# being used in a runAsRoot script.
2015-11-19 12:11:17 +00:00
shadowSetup = ''
export PATH = $ { shadow } /bin : $ PATH
mkdir - p /etc/pam.d
2016-09-27 23:42:05 +00:00
if [ [ ! - f /etc/passwd ] ] ; then
2019-02-26 11:45:54 +00:00
echo " r o o t : x : 0 : 0 : : / r o o t : ${ runtimeShell } " > /etc/passwd
2015-11-19 12:11:17 +00:00
echo " r o o t : ! x : : : : : : : " > /etc/shadow
fi
2016-09-27 23:42:05 +00:00
if [ [ ! - f /etc/group ] ] ; then
2015-11-19 12:11:17 +00:00
echo " r o o t : x : 0 : " > /etc/group
echo " r o o t : x : : " > /etc/gshadow
fi
2016-09-27 23:42:05 +00:00
if [ [ ! - f /etc/pam.d/other ] ] ; then
2015-11-19 12:11:17 +00:00
cat > /etc/pam.d/other < < EOF
2016-09-27 23:42:05 +00:00
account sufficient pam_unix . so
auth sufficient pam_rootok . so
2023-03-12 16:59:01 +00:00
password requisite pam_unix . so nullok yescrypt
2016-09-27 23:42:05 +00:00
session required pam_unix . so
EOF
2015-11-19 12:11:17 +00:00
fi
2016-09-27 23:42:05 +00:00
if [ [ ! - f /etc/login.defs ] ] ; then
2015-11-19 12:11:17 +00:00
touch /etc/login.defs
fi
'' ;
2016-09-27 23:42:05 +00:00
# Run commands in a virtual machine.
2021-08-10 08:24:15 +00:00
runWithOverlay =
{ name
, fromImage ? null
, fromImageName ? null
, fromImageTag ? null
, diskSize ? 1024
2022-08-16 04:09:42 +00:00
, buildVMMemorySize ? 512
2021-08-10 08:24:15 +00:00
, preMount ? " "
, postMount ? " "
, postUmount ? " "
} :
2021-09-29 07:34:06 +00:00
vmTools . runInLinuxVM (
2021-08-10 08:24:15 +00:00
runCommand name
{
preVM = vmTools . createEmptyImage {
size = diskSize ;
fullName = " d o c k e r - r u n - d i s k " ;
2021-09-29 07:34:06 +00:00
destination = " . / i m a g e " ;
2021-08-10 08:24:15 +00:00
} ;
inherit fromImage fromImageName fromImageTag ;
2022-08-16 04:09:42 +00:00
memSize = buildVMMemorySize ;
2021-08-10 08:24:15 +00:00
nativeBuildInputs = [ util-linux e2fsprogs jshon rsync jq ] ;
} ''
mkdir disk
mkfs /dev / $ { vmTools . hd }
mount /dev / $ { vmTools . hd } disk
cd disk
2023-02-06 18:19:29 +00:00
function dedup ( ) {
declare - A seen
while read ln ; do
if [ [ - z " ' ' ${ seen [ " $ l n " ] : - } " ] ] ; then
echo " $ l n " ; seen [ " $ l n " ] = 1
fi
done
}
2021-08-10 08:24:15 +00:00
if [ [ - n " $ f r o m I m a g e " ] ] ; then
echo " U n p a c k i n g b a s e i m a g e . . . "
mkdir image
tar - C image - xpf " $ f r o m I m a g e "
if [ [ - n " $ f r o m I m a g e N a m e " ] ] && [ [ - n " $ f r o m I m a g e T a g " ] ] ; then
parentID = " $ (
cat " i m a g e / m a n i f e s t . j s o n " |
jq - r ' . [ ] | select ( . RepoTags | contains ( [ $ desiredTag ] ) ) | rtrimstr ( " . j s o n " ) ' \
- - arg desiredTag " $ f r o m I m a g e N a m e : $ f r o m I m a g e T a g "
) "
else
echo " F r o m - i m a g e n a m e o r t a g w a s n ' t s e t . R e a d i n g t h e f i r s t I D . "
parentID = " $ ( c a t " image/manifest.json " | j q - r ' . [ 0 ] . C o n f i g | r t r i m s t r ( " . json " ) ' ) "
fi
2017-09-28 10:56:23 +00:00
2023-02-06 18:19:29 +00:00
# In case of repeated layers, unpack only the last occurrence of each
cat ./image/manifest.json | jq - r ' . [ 0 ] . Layers | . [ ] ' | tac | dedup | tac > layer-list
2021-08-10 08:24:15 +00:00
else
touch layer-list
fi
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
# Unpack all of the parent layers into the image.
lowerdir = " "
extractionID = 0
2021-12-18 00:28:10 +00:00
for layerTar in $ ( cat layer-list ) ; do
2021-08-10 08:24:15 +00:00
echo " U n p a c k i n g l a y e r $ l a y e r T a r "
extractionID = $ ( ( extractionID + 1 ) )
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
2021-08-10 08:24:15 +00:00
mkdir - p image / $ extractionID/layer
tar - C image / $ extractionID/layer - xpf image / $ layerTar
2023-02-06 18:19:29 +00:00
rm image / $ layerTar
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
find image / $ extractionID/layer - name " . w h . * " - exec bash - c ' name = " $ ( b a s e n a m e { } | s e d " s / ^ . wh . // " ) " ; mknod " $ ( d i r n a m e { } ) / $ n a m e " c 0 0 ; rm { } ' \ ;
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
# Get the next lower directory and continue the loop.
lowerdir = image / $ extractionID/layer '' ${ lowerdir:+: } $l o w e r d i r
done
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
mkdir work
mkdir layer
mkdir mnt
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
$ { lib . optionalString ( preMount != " " ) ''
# Execute pre-mount steps
echo " E x e c u t i n g p r e - m o u n t s t e p s . . . "
$ { preMount }
'' }
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
if [ - n " $ l o w e r d i r " ] ; then
mount - t overlay overlay - olowerdir = $ lowerdir , workdir = work , upperdir = layer mnt
else
mount - - bind layer mnt
fi
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
$ { lib . optionalString ( postMount != " " ) ''
# Execute post-mount steps
echo " E x e c u t i n g p o s t - m o u n t s t e p s . . . "
$ { postMount }
'' }
2016-09-27 23:42:05 +00:00
2021-08-10 08:24:15 +00:00
umount mnt
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
(
cd layer
cmd = ' name = " $ ( b a s e n a m e { } ) " ; touch " $ ( d i r n a m e { } ) / . w h . $ n a m e " ; rm " { } " '
find . - type c - exec bash - c " $ c m d " \ ;
)
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
$ { postUmount }
'' ) ;
2015-11-19 12:11:17 +00:00
exportImage = { name ? fromImage . name , fromImage , fromImageName ? null , fromImageTag ? null , diskSize ? 1024 }:
runWithOverlay {
inherit name fromImage fromImageName fromImageTag diskSize ;
postMount = ''
2016-09-27 23:42:05 +00:00
echo " P a c k i n g r a w i m a g e . . . "
2020-02-13 22:27:07 +00:00
tar - C mnt - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - cf $ out/layer.tar .
2015-11-19 12:11:17 +00:00
'' ;
2021-09-29 07:34:45 +00:00
postUmount = ''
mv $ out/layer.tar .
rm - rf $ out
mv layer . tar $ out
'' ;
2015-11-19 12:11:17 +00:00
} ;
2016-09-27 23:42:05 +00:00
# Create an executable shell script which has the coreutils in its
# PATH. Since root scripts are executed in a blank environment, even
# things like `ls` or `echo` will be missing.
shellScript = name : text :
writeScript name ''
2019-02-26 11:45:54 +00:00
#!${runtimeShell}
2016-09-27 23:42:05 +00:00
set - e
export PATH = $ { coreutils } /bin : /bin
$ { text }
'' ;
# Create a "layer" (set of files).
2021-08-10 08:24:15 +00:00
mkPureLayer =
{
# Name of the layer
name
, # JSON containing configuration and metadata for this layer.
baseJson
, # Files to add to the layer.
2022-07-01 13:02:00 +00:00
copyToRoot ? null
2021-08-10 08:24:15 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # Additional commands to run on the layer before it is tar'd up.
extraCommands ? " "
, uid ? 0
, gid ? 0
} :
runCommand " d o c k e r - l a y e r - ${ name } "
{
2022-07-01 13:02:00 +00:00
inherit baseJson extraCommands ;
contents = copyToRoot ;
2021-08-10 08:24:15 +00:00
nativeBuildInputs = [ jshon rsync tarsum ] ;
}
''
mkdir layer
if [ [ - n " $ c o n t e n t s " ] ] ; then
echo " A d d i n g c o n t e n t s . . . "
for item in $ contents ; do
echo " A d d i n g $ i t e m "
rsync - a $ { if keepContentsDirlinks then " K " else " k " } - - chown = 0 : 0 $ item / layer /
done
else
echo " N o c o n t e n t s t o a d d t o l a y e r . "
fi
2016-09-27 23:42:05 +00:00
2021-08-10 08:24:15 +00:00
chmod ug + w layer
2017-07-01 14:59:15 +00:00
2021-08-10 08:24:15 +00:00
if [ [ - n " $ e x t r a C o m m a n d s " ] ] ; then
( cd layer ; eval " $ e x t r a C o m m a n d s " )
fi
2015-11-19 12:11:17 +00:00
2021-08-10 08:24:15 +00:00
# Tar up the layer and throw it into 'layer.tar'.
echo " P a c k i n g l a y e r . . . "
mkdir $ out
tarhash = $ ( tar - C layer - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = $ { toString uid } - - group = $ { toString gid } - cf - . | tee - p $ out/layer.tar | tarsum )
2016-09-27 23:42:05 +00:00
2021-08-10 08:24:15 +00:00
# Add a 'checksum' field to the JSON, with the value set to the
# checksum of the tarball.
cat $ { baseJson } | jshon - s " $ t a r h a s h " - i checksum > $ out/json
2016-09-27 23:42:05 +00:00
2021-08-10 08:24:15 +00:00
# Indicate to docker that we're using schema version 1.0.
echo - n " 1 . 0 " > $ out/VERSION
2016-09-27 23:42:05 +00:00
2021-08-10 08:24:15 +00:00
echo " F i n i s h e d b u i l d i n g l a y e r ' ${ name } ' "
'' ;
2015-11-19 12:11:17 +00:00
2016-09-27 23:42:05 +00:00
# Make a "root" layer; required if we need to execute commands as a
# privileged user on the image. The commands themselves will be
# performed in a virtual machine sandbox.
2021-08-10 08:24:15 +00:00
mkRootLayer =
{
# Name of the image.
name
, # Script to run as root. Bash.
runAsRoot
, # Files to add to the layer. If null, an empty layer will be created.
2022-07-01 13:02:00 +00:00
# To add packages to /bin, use `buildEnv` or similar.
copyToRoot ? null
2021-08-10 08:24:15 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # JSON containing configuration and metadata for this layer.
baseJson
, # Existing image onto which to append the new layer.
fromImage ? null
, # Name of the image we're appending onto.
fromImageName ? null
, # Tag of the image we're appending onto.
fromImageTag ? null
, # How much disk to allocate for the temporary virtual machine.
diskSize ? 1024
2022-08-16 04:09:42 +00:00
, # How much memory to allocate for the temporary virtual machine.
buildVMMemorySize ? 512
2021-08-10 08:24:15 +00:00
, # Commands (bash) to run on the layer; these do not require sudo.
extraCommands ? " "
} :
2016-09-27 23:42:05 +00:00
# Generate an executable script from the `runAsRoot` text.
2018-05-22 23:53:28 +00:00
let
runAsRootScript = shellScript " r u n - a s - r o o t . s h " runAsRoot ;
extraCommandsScript = shellScript " e x t r a - c o m m a n d s . s h " extraCommands ;
2021-08-10 08:24:15 +00:00
in
runWithOverlay {
2016-09-27 23:42:05 +00:00
name = " d o c k e r - l a y e r - ${ name } " ;
2022-08-16 04:09:42 +00:00
inherit fromImage fromImageName fromImageTag diskSize buildVMMemorySize ;
2015-11-19 12:11:17 +00:00
2022-07-01 13:02:00 +00:00
preMount = lib . optionalString ( copyToRoot != null && copyToRoot != [ ] ) ''
2016-09-27 23:42:05 +00:00
echo " A d d i n g c o n t e n t s . . . "
2022-07-01 13:02:00 +00:00
for item in $ { escapeShellArgs ( map ( c : " ${ c } " ) ( toList copyToRoot ) ) } ; do
2016-09-27 23:42:05 +00:00
echo " A d d i n g $ i t e m . . . "
2017-04-23 13:45:21 +00:00
rsync - a $ { if keepContentsDirlinks then " K " else " k " } - - chown = 0 : 0 $ item / layer /
2015-11-19 12:11:17 +00:00
done
2017-07-01 14:59:15 +00:00
chmod ug + w layer
2015-11-19 12:11:17 +00:00
'' ;
postMount = ''
2023-10-09 20:15:41 +00:00
mkdir - p mnt / { dev , proc , sys , tmp } mnt $ { storeDir }
2016-09-27 23:42:05 +00:00
# Mount /dev, /sys and the nix store as shared folders.
2015-11-19 12:11:17 +00:00
mount - - rbind /dev mnt/dev
mount - - rbind /sys mnt/sys
2016-09-27 23:42:05 +00:00
mount - - rbind $ { storeDir } mnt $ { storeDir }
2015-11-19 12:11:17 +00:00
2016-09-27 23:42:05 +00:00
# Execute the run as root script. See 'man unshare' for
# details on what's going on here; basically this command
# means that the runAsRootScript will be executed in a nearly
# completely isolated environment.
2020-11-04 07:11:05 +00:00
#
# Ideally we would use --mount-proc=mnt/proc or similar, but this
# doesn't work. The workaround is to setup proc after unshare.
# See: https://github.com/karelzak/util-linux/issues/648
unshare - imnpuf - - mount-proc sh - c ' mount - - rbind /proc mnt/proc && chroot mnt $ { runAsRootScript } '
2016-09-27 23:42:05 +00:00
# Unmount directories and remove them.
umount - R mnt/dev mnt/sys mnt $ { storeDir }
rmdir - - ignore-fail-on-non-empty \
mnt/dev mnt/proc mnt/sys mnt $ { storeDir } \
mnt $ ( dirname $ { storeDir } )
2015-11-19 12:11:17 +00:00
'' ;
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
postUmount = ''
2018-05-22 23:53:28 +00:00
( cd layer ; $ { extraCommandsScript } )
2015-11-19 12:11:17 +00:00
2016-09-27 23:42:05 +00:00
echo " P a c k i n g l a y e r . . . "
2019-06-27 18:15:42 +00:00
mkdir - p $ out
2020-05-06 09:17:47 +00:00
tarhash = $ ( tar - C layer - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - cf - . |
2020-11-03 11:33:11 +00:00
tee - p $ out/layer.tar |
2020-05-06 09:17:47 +00:00
$ { tarsum } /bin/tarsum )
2016-09-27 23:42:05 +00:00
2018-09-25 17:54:45 +00:00
cat $ { baseJson } | jshon - s " $ t a r h a s h " - i checksum > $ out/json
2016-09-27 23:42:05 +00:00
# Indicate to docker that we're using schema version 1.0.
2015-11-19 12:11:17 +00:00
echo - n " 1 . 0 " > $ out/VERSION
2016-09-27 23:42:05 +00:00
echo " F i n i s h e d b u i l d i n g l a y e r ' ${ name } ' "
2015-11-19 12:11:17 +00:00
'' ;
} ;
2024-02-17 16:48:57 +00:00
buildLayeredImage = lib . makeOverridable ( { name , compressor ? " g z " , . . . } @ args :
dockerTools.buildLayeredImage: init
Create a many-layered Docker Image.
Implements much less than buildImage:
- Doesn't support specific uids/gids
- Doesn't support runninng commands after building
- Doesn't require qemu
- Doesn't create mutable copies of the files in the path
- Doesn't support parent images
If you want those feature, I recommend using buildLayeredImage as an
input to buildImage.
Notably, it does support:
- Caching low level, common paths based on a graph traversial
algorithm, see referencesByPopularity in
0a80233487993256e811f566b1c80a40394c03d6
- Configurable number of layers. If you're not using AUFS or not
extending the image, you can specify a larger number of layers at
build time:
pkgs.dockerTools.buildLayeredImage {
name = "hello";
maxLayers = 128;
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
- Parallelized creation of the layers, improving build speed.
- The contents of the image includes the closure of the configuration,
so you don't have to specify paths in contents and config.
With buildImage, paths referred to by the config were not included
automatically in the image. Thus, if you wanted to call Git, you
had to specify it twice:
pkgs.dockerTools.buildImage {
name = "hello";
contents = [ pkgs.gitFull ];
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
buildLayeredImage on the other hand includes the runtime closure of
the config when calculating the contents of the image:
pkgs.dockerTools.buildImage {
name = "hello";
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
Minor Problems
- If any of the store paths change, every layer will be rebuilt in
the nix-build. However, beacuse the layers are bit-for-bit
reproducable, when these images are loaded in to Docker they will
match existing layers and not be imported or uploaded twice.
Common Questions
- Aren't Docker layers ordered?
No. People who have used a Dockerfile before assume Docker's
Layers are inherently ordered. However, this is not true -- Docker
layers are content-addressable and are not explicitly layered until
they are composed in to an Image.
- What happens if I have more than maxLayers of store paths?
The first (maxLayers-2) most "popular" paths will have their own
individual layers, then layer #(maxLayers-1) will contain all the
remaining "unpopular" paths, and finally layer #(maxLayers) will
contain the Image configuration.
2018-09-25 14:53:42 +00:00
let
2024-02-28 11:13:58 +00:00
stream = streamLayeredImage ( builtins . removeAttrs args [ " c o m p r e s s o r " ] ) ;
2024-02-17 16:48:57 +00:00
compress = compressorForImage compressor name ;
dockerTools.buildLayeredImage: init
Create a many-layered Docker Image.
Implements much less than buildImage:
- Doesn't support specific uids/gids
- Doesn't support runninng commands after building
- Doesn't require qemu
- Doesn't create mutable copies of the files in the path
- Doesn't support parent images
If you want those feature, I recommend using buildLayeredImage as an
input to buildImage.
Notably, it does support:
- Caching low level, common paths based on a graph traversial
algorithm, see referencesByPopularity in
0a80233487993256e811f566b1c80a40394c03d6
- Configurable number of layers. If you're not using AUFS or not
extending the image, you can specify a larger number of layers at
build time:
pkgs.dockerTools.buildLayeredImage {
name = "hello";
maxLayers = 128;
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
- Parallelized creation of the layers, improving build speed.
- The contents of the image includes the closure of the configuration,
so you don't have to specify paths in contents and config.
With buildImage, paths referred to by the config were not included
automatically in the image. Thus, if you wanted to call Git, you
had to specify it twice:
pkgs.dockerTools.buildImage {
name = "hello";
contents = [ pkgs.gitFull ];
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
buildLayeredImage on the other hand includes the runtime closure of
the config when calculating the contents of the image:
pkgs.dockerTools.buildImage {
name = "hello";
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
Minor Problems
- If any of the store paths change, every layer will be rebuilt in
the nix-build. However, beacuse the layers are bit-for-bit
reproducable, when these images are loaded in to Docker they will
match existing layers and not be imported or uploaded twice.
Common Questions
- Aren't Docker layers ordered?
No. People who have used a Dockerfile before assume Docker's
Layers are inherently ordered. However, this is not true -- Docker
layers are content-addressable and are not explicitly layered until
they are composed in to an Image.
- What happens if I have more than maxLayers of store paths?
The first (maxLayers-2) most "popular" paths will have their own
individual layers, then layer #(maxLayers-1) will contain all the
remaining "unpopular" paths, and finally layer #(maxLayers) will
contain the Image configuration.
2018-09-25 14:53:42 +00:00
in
2024-02-17 16:48:57 +00:00
runCommand " ${ baseNameOf name } . t a r ${ compress . ext } "
2021-08-10 08:24:15 +00:00
{
2020-06-08 09:47:46 +00:00
inherit ( stream ) imageName ;
2024-03-23 01:33:22 +00:00
passthru = { inherit ( stream ) imageTag ; inherit stream ; } ;
2024-02-17 16:48:57 +00:00
nativeBuildInputs = compress . nativeInputs ;
} " ${ stream } | ${ compress . compress } > $ o u t "
2023-01-01 23:43:00 +00:00
) ;
dockerTools.buildLayeredImage: init
Create a many-layered Docker Image.
Implements much less than buildImage:
- Doesn't support specific uids/gids
- Doesn't support runninng commands after building
- Doesn't require qemu
- Doesn't create mutable copies of the files in the path
- Doesn't support parent images
If you want those feature, I recommend using buildLayeredImage as an
input to buildImage.
Notably, it does support:
- Caching low level, common paths based on a graph traversial
algorithm, see referencesByPopularity in
0a80233487993256e811f566b1c80a40394c03d6
- Configurable number of layers. If you're not using AUFS or not
extending the image, you can specify a larger number of layers at
build time:
pkgs.dockerTools.buildLayeredImage {
name = "hello";
maxLayers = 128;
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
- Parallelized creation of the layers, improving build speed.
- The contents of the image includes the closure of the configuration,
so you don't have to specify paths in contents and config.
With buildImage, paths referred to by the config were not included
automatically in the image. Thus, if you wanted to call Git, you
had to specify it twice:
pkgs.dockerTools.buildImage {
name = "hello";
contents = [ pkgs.gitFull ];
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
buildLayeredImage on the other hand includes the runtime closure of
the config when calculating the contents of the image:
pkgs.dockerTools.buildImage {
name = "hello";
config.Cmd = [ "${pkgs.gitFull}/bin/git" ];
};
Minor Problems
- If any of the store paths change, every layer will be rebuilt in
the nix-build. However, beacuse the layers are bit-for-bit
reproducable, when these images are loaded in to Docker they will
match existing layers and not be imported or uploaded twice.
Common Questions
- Aren't Docker layers ordered?
No. People who have used a Dockerfile before assume Docker's
Layers are inherently ordered. However, this is not true -- Docker
layers are content-addressable and are not explicitly layered until
they are composed in to an Image.
- What happens if I have more than maxLayers of store paths?
The first (maxLayers-2) most "popular" paths will have their own
individual layers, then layer #(maxLayers-1) will contain all the
remaining "unpopular" paths, and finally layer #(maxLayers) will
contain the Image configuration.
2018-09-25 14:53:42 +00:00
2015-11-19 12:11:17 +00:00
# 1. extract the base image
# 2. create the layer
# 3. add layer deps to the layer itself, diffing with the base image
# 4. compute the layer id
# 5. put the layer in the image
# 6. repack the image
2023-01-01 23:43:00 +00:00
buildImage = lib . makeOverridable (
2021-08-10 08:24:15 +00:00
args @ {
# Image name.
name
, # Image tag, when null then the nix output hash will be used.
tag ? null
, # Parent image, to append to.
fromImage ? null
, # Name of the parent image; will be read from the image otherwise.
fromImageName ? null
, # Tag of the parent image; will be read from the image otherwise.
fromImageTag ? null
, # Files to put on the image (a nix store path or list of paths).
2022-07-01 13:02:00 +00:00
copyToRoot ? null
2021-08-10 08:24:15 +00:00
, # When copying the contents into the image, preserve symlinks to
# directories (see `rsync -K`). Otherwise, transform those symlinks
# into directories.
keepContentsDirlinks ? false
, # Docker config; e.g. what command to run on the container.
config ? null
2022-11-21 12:10:07 +00:00
, # Image architecture, defaults to the architecture of the `hostPlatform` when unset
2022-12-07 13:07:47 +00:00
architecture ? defaultArchitecture
2021-08-10 08:24:15 +00:00
, # Optional bash script to run on the files prior to fixturizing the layer.
extraCommands ? " "
, uid ? 0
, gid ? 0
, # Optional bash script to run as root on the image when provisioning.
runAsRoot ? null
, # Size of the virtual machine disk to provision when building the image.
diskSize ? 1024
2022-08-16 04:09:42 +00:00
, # Size of the virtual machine memory to provision when building the image.
buildVMMemorySize ? 512
2021-08-10 08:24:15 +00:00
, # Time of creation of the image.
created ? " 1 9 7 0 - 0 1 - 0 1 T 0 0 : 0 0 : 0 1 Z "
2024-02-17 16:48:57 +00:00
, # Compressor to use. One of: none, gz, zstd.
compressor ? " g z "
2024-05-06 12:57:08 +00:00
# Populate the nix database in the image with the dependencies of `copyToRoot`.
, includeNixDB ? false
2022-07-01 13:02:00 +00:00
, # Deprecated.
contents ? null
2021-08-10 08:24:15 +00:00
,
} :
2015-11-19 12:11:17 +00:00
let
2022-07-01 13:02:00 +00:00
checked =
lib . warnIf ( contents != null )
" i n d o c k e r i m a g e ${ name } : T h e c o n t e n t s p a r a m e t e r i s d e p r e c a t e d . C h a n g e t o c o p y T o R o o t i f t h e c o n t e n t s a r e d e s i g n e d t o b e c o p i e d t o t h e r o o t f i l e s y s t e m , s u c h a s w h e n y o u u s e ` b u i l d E n v ` o r s i m i l a r b e t w e e n c o n t e n t s a n d y o u r p a c k a g e s . U s e c o p y T o R o o t = b u i l d E n v { . . . } ; o r s i m i l a r i f y o u i n t e n d t o a d d p a c k a g e s t o / b i n . "
lib . throwIf ( contents != null && copyToRoot != null ) " i n d o c k e r i m a g e ${ name } : Y o u c a n n o t s p e c i f y b o t h c o n t e n t s a n d c o p y T o R o o t . "
;
rootContents = if copyToRoot == null then contents else copyToRoot ;
2016-02-18 16:16:15 +00:00
baseName = baseNameOf name ;
2016-09-27 23:42:05 +00:00
# Create a JSON blob of the configuration. Set the date to unix zero.
2021-08-10 08:24:15 +00:00
baseJson =
let
2018-09-20 15:40:36 +00:00
pure = writeText " ${ baseName } - c o n f i g . j s o n " ( builtins . toJSON {
2022-11-21 12:10:07 +00:00
inherit created config architecture ;
2022-11-18 14:31:53 +00:00
preferLocalBuild = true ;
2018-09-20 15:40:36 +00:00
os = " l i n u x " ;
} ) ;
impure = runCommand " ${ baseName } - c o n f i g . j s o n "
2022-11-18 14:31:53 +00:00
{
nativeBuildInputs = [ jq ] ;
preferLocalBuild = true ;
}
2018-09-20 15:40:36 +00:00
''
2021-08-10 08:24:15 +00:00
jq " . c r e a t e d = \" $ ( T Z = u t c d a t e - - i s o - 8 6 0 1 = " seconds " ) \" " $ { pure } > $ out
2018-09-20 15:40:36 +00:00
'' ;
2021-08-10 08:24:15 +00:00
in
if created == " n o w " then impure else pure ;
2016-03-10 07:29:28 +00:00
2024-02-17 16:48:57 +00:00
compress = compressorForImage compressor name ;
2024-05-06 12:57:08 +00:00
# TODO: add the dependencies of the config json.
extraCommandsWithDB =
if includeNixDB then ( mkDbExtraCommand rootContents ) + extraCommands
else extraCommands ;
2016-09-27 23:42:05 +00:00
layer =
if runAsRoot == null
2021-08-10 08:24:15 +00:00
then
mkPureLayer
{
name = baseName ;
2024-05-06 12:57:08 +00:00
inherit baseJson keepContentsDirlinks uid gid ;
extraCommands = extraCommandsWithDB ;
2022-07-01 13:02:00 +00:00
copyToRoot = rootContents ;
2021-08-10 08:24:15 +00:00
} else
mkRootLayer {
name = baseName ;
inherit baseJson fromImage fromImageName fromImageTag
2024-05-06 12:57:08 +00:00
keepContentsDirlinks runAsRoot diskSize buildVMMemorySize ;
extraCommands = extraCommandsWithDB ;
2022-07-01 13:02:00 +00:00
copyToRoot = rootContents ;
2021-08-10 08:24:15 +00:00
} ;
2024-02-17 16:48:57 +00:00
result = runCommand " d o c k e r - i m a g e - ${ baseName } . t a r ${ compress . ext } "
2021-08-10 08:24:15 +00:00
{
2024-02-17 16:48:57 +00:00
nativeBuildInputs = [ jshon jq moreutils ] ++ compress . nativeInputs ;
2021-08-10 08:24:15 +00:00
# Image name must be lowercase
imageName = lib . toLower name ;
2023-06-24 18:19:19 +00:00
imageTag = lib . optionalString ( tag != null ) tag ;
2021-08-10 08:24:15 +00:00
inherit fromImage baseJson ;
2024-03-15 00:38:19 +00:00
layerClosure = writeClosure [ layer ] ;
2021-08-10 08:24:15 +00:00
passthru . buildArgs = args ;
passthru . layer = layer ;
passthru . imageTag =
if tag != null
2021-06-01 12:42:21 +00:00
then tag
2020-07-11 13:51:58 +00:00
else
2024-03-20 16:54:09 +00:00
lib . head ( lib . strings . splitString " - " ( baseNameOf ( builtins . unsafeDiscardStringContext result . outPath ) ) ) ;
2021-08-10 08:24:15 +00:00
} ''
2018-06-29 17:20:55 +00:00
$ { lib . optionalString ( tag == null ) ''
outName = " $ ( b a s e n a m e " $ out " ) "
outHash = $ ( echo " $ o u t N a m e " | cut - d - - f 1 )
imageTag = $ outHash
'' }
2016-09-23 21:10:47 +00:00
# Print tar contents:
# 1: Interpreted as relative to the root directory
# 2: With no trailing slashes on directories
2016-09-27 23:42:05 +00:00
# This is useful for ensuring that the output matches the
# values generated by the "find" command
2016-09-23 21:10:47 +00:00
ls_tar ( ) {
2016-09-27 23:42:05 +00:00
for f in $ ( tar - tf $ 1 | xargs realpath - ms - - relative-to = . ) ; do
if [ [ " $ f " != " . " ] ] ; then
echo " / $ f "
fi
done
2016-09-23 21:10:47 +00:00
}
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
mkdir image
touch baseFiles
2020-05-08 09:49:16 +00:00
baseEnvs = ' [ ] '
2016-09-27 23:42:05 +00:00
if [ [ - n " $ f r o m I m a g e " ] ] ; then
echo " U n p a c k i n g b a s e i m a g e . . . "
2015-11-19 12:11:17 +00:00
tar - C image - xpf " $ f r o m I m a g e "
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
2020-05-08 09:49:16 +00:00
# Store the layers and the environment variables from the base image
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
cat ./image/manifest.json | jq - r ' . [ 0 ] . Layers | . [ ] ' > layer-list
2020-05-08 09:49:16 +00:00
configName = " $ ( c a t . / i m a g e / m a n i f e s t . j s o n | j q - r ' . [ 0 ] . C o n f i g ' ) "
baseEnvs = " $ ( c a t " ./image / $ configName " | j q ' . c o n f i g . E n v / / [ ] ' ) "
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
2020-06-08 09:47:46 +00:00
# Extract the parentID from the manifest
if [ [ - n " $ f r o m I m a g e N a m e " ] ] && [ [ - n " $ f r o m I m a g e T a g " ] ] ; then
parentID = " $ (
cat " i m a g e / m a n i f e s t . j s o n " |
jq - r ' . [ ] | select ( . RepoTags | contains ( [ $ desiredTag ] ) ) | rtrimstr ( " . j s o n " ) ' \
- - arg desiredTag " $ f r o m I m a g e N a m e : $ f r o m I m a g e T a g "
) "
else
echo " F r o m - i m a g e n a m e o r t a g w a s n ' t s e t . R e a d i n g t h e f i r s t I D . "
parentID = " $ ( c a t " image/manifest.json " | j q - r ' . [ 0 ] . C o n f i g | r t r i m s t r ( " . json " ) ' ) "
fi
2020-05-08 09:49:16 +00:00
# Otherwise do not import the base image configuration and manifest
2017-09-28 10:56:23 +00:00
chmod a + w image image /* . j s o n
rm - f image /* . j s o n
for l in image /* / l a y e r . t a r ; d o
ls_tar $ l > > baseFiles
2015-11-19 12:11:17 +00:00
done
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
else
touch layer-list
2015-11-19 12:11:17 +00:00
fi
chmod - R ug + rw image
2016-09-27 23:42:05 +00:00
2015-11-19 12:11:17 +00:00
mkdir temp
cp $ { layer } /* t e m p /
chmod ug + w temp /*
2016-03-10 07:29:28 +00:00
for dep in $ ( cat $ layerClosure ) ; do
2016-09-27 23:42:05 +00:00
find $ dep > > layerFiles
2016-03-10 07:29:28 +00:00
done
2016-09-27 23:42:05 +00:00
echo " A d d i n g l a y e r . . . "
# Record the contents of the tarball with ls_tar.
2016-09-23 21:10:47 +00:00
ls_tar temp/layer.tar > > baseFiles
2016-03-10 07:29:28 +00:00
2018-04-12 11:03:56 +00:00
# Append nix/store directory to the layer so that when the layer is loaded in the
# image /nix/store has read permissions for non-root users.
# nix/store is added only if the layer has /nix/store paths in it.
if [ $ ( wc - l < $ layerClosure ) - gt 1 ] && [ $ ( grep - c - e " ^ / n i x / s t o r e $ " baseFiles ) - eq 0 ] ; then
mkdir - p nix/store
chmod - R 555 nix
echo " . / n i x " > > layerFiles
echo " . / n i x / s t o r e " > > layerFiles
fi
2016-09-27 23:42:05 +00:00
# Get the files in the new layer which were *not* present in
# the old layer, and record them as newFiles.
comm < ( sort - n baseFiles | uniq ) \
< ( sort - n layerFiles | uniq | grep - v $ { layer } ) -1 -3 > newFiles
# Append the new files to the layer.
2018-03-12 17:26:15 +00:00
tar - rpf temp/layer.tar - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " \
2021-09-10 17:12:32 +00:00
- - owner = 0 - - group = 0 - - no-recursion - - verbatim-files-from - - files-from newFiles
2016-09-27 23:42:05 +00:00
2017-09-28 10:56:23 +00:00
echo " A d d i n g m e t a . . . "
# If we have a parentID, add it to the json metadata.
if [ [ - n " $ p a r e n t I D " ] ] ; then
cat temp/json | jshon - s " $ p a r e n t I D " - i parent > tmpjson
mv tmpjson temp/json
fi
# Take the sha256 sum of the generated json and use it as the layer ID.
# Compute the size and add it to the json under the 'Size' field.
layerID = $ ( sha256sum temp/json | cut - d ' ' - f 1 )
size = $ ( stat - - printf = " % s " temp/layer.tar )
cat temp/json | jshon - s " $ l a y e r I D " - i id - n $ size - i Size > tmpjson
mv tmpjson temp/json
# Use the temp folder we've been working on to create a new image.
mv temp image / $ layerID
2016-09-27 23:42:05 +00:00
2019-04-30 06:42:24 +00:00
# Add the new layer ID to the end of the layer list
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
(
2019-04-30 06:42:24 +00:00
cat layer-list
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
# originally this used `sed -i "1i$layerID" layer-list`, but
# would fail if layer-list was completely empty.
echo " $ l a y e r I D / l a y e r . t a r "
2019-06-27 18:15:42 +00:00
) | sponge layer-list
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
2017-09-28 10:56:23 +00:00
# Create image json and image manifest
2020-05-08 09:49:16 +00:00
imageJson = $ ( cat $ { baseJson } | jq ' . config . Env = $ baseenv + . config . Env' - - argjson baseenv " $ b a s e E n v s " )
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . + { \" r o o t f s \" : { \" d i f f _ i d s \" : [ ] , \" t y p e \" : \" l a y e r s \" } } " )
2017-08-02 17:27:19 +00:00
manifestJson = $ ( jq - n " [ { \" R e p o T a g s \" : [ \" $ i m a g e N a m e : $ i m a g e T a g \" ] } ] " )
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
2019-04-30 06:42:24 +00:00
for layerTar in $ ( cat ./layer-list ) ; do
dockerTools.buildImage: support using a layered image in fromImage
Docker images used to be, essentially, a linked list of layers. Each
layer would have a tarball and a json document pointing to its parent,
and the image pointed to the top layer:
imageA ----> layerA
|
v
layerB
|
v
layerC
The current image spec changed this format to where the Image defined
the order and set of layers:
imageA ---> layerA
|--> layerB
`--> layerC
For backwards compatibility, docker produces images which follow both
specs: layers point to parents, and images also point to the entire
list:
imageA ---> layerA
| |
| v
|--> layerB
| |
| v
`--> layerC
This is nice for tooling which supported the older version and never
updated to support the newer format.
Our `buildImage` code only supported the old version, so in order for
`buildImage` to properly generate an image based on another image
with `fromImage`, the parent image's layers must fully support the old
mechanism.
This is not a problem in general, but is a problem with
`buildLayeredImage`.
`buildLayeredImage` creates images with newer image spec, because
individual store paths don't have a guaranteed parent layer. Including
a specific parent ID in the layer's json makes the output less likely
to cache hit when published or pulled.
This means until now, `buildLayeredImage` could not be the input to
`buildImage`.
The changes in this PR change `buildImage` to only use the layer's
manifest when locating parent IDs. This does break buildImage on
extremely old Docker images, though I do wonder how many of these
exist.
This work has been sponsored by Target.
2018-12-04 17:18:06 +00:00
layerChecksum = $ ( sha256sum image / $ layerTar | cut - d ' ' - f1 )
2019-04-30 06:42:24 +00:00
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . h i s t o r y | = . + [ { \" c r e a t e d \" : \" $ ( j q - r . c r e a t e d ${ baseJson } ) \" } ] " )
# diff_ids order is from the bottom-most to top-most layer
imageJson = $ ( echo " $ i m a g e J s o n " | jq " . r o o t f s . d i f f _ i d s | = . + [ \" s h a 2 5 6 : $ l a y e r C h e c k s u m \" ] " )
manifestJson = $ ( echo " $ m a n i f e s t J s o n " | jq " . [ 0 ] . L a y e r s | = . + [ \" $ l a y e r T a r \" ] " )
2017-08-02 17:27:19 +00:00
done
imageJsonChecksum = $ ( echo " $ i m a g e J s o n " | sha256sum | cut - d ' ' - f1 )
2017-09-28 10:56:23 +00:00
echo " $ i m a g e J s o n " > " i m a g e / $ i m a g e J s o n C h e c k s u m . j s o n "
manifestJson = $ ( echo " $ m a n i f e s t J s o n " | jq " . [ 0 ] . C o n f i g = \" $ i m a g e J s o n C h e c k s u m . j s o n \" " )
2017-08-02 17:27:19 +00:00
echo " $ m a n i f e s t J s o n " > image/manifest.json
2017-07-26 19:53:35 +00:00
2017-09-28 10:56:23 +00:00
# Store the json under the name image/repositories.
jshon - n object \
- n object - s " $ l a y e r I D " - i " $ i m a g e T a g " \
- i " $ i m a g e N a m e " > image/repositories
2016-09-27 23:42:05 +00:00
# Make the image read-only.
2015-11-19 12:11:17 +00:00
chmod - R a-w image
2016-09-27 23:42:05 +00:00
echo " C o o k i n g t h e i m a g e . . . "
2024-02-17 16:48:57 +00:00
tar - C image - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = 0 - - group = 0 - - xform s:' ^ . / ' : : - c . | $ { compress . compress } > $ out
2016-09-27 23:42:05 +00:00
echo " F i n i s h e d . "
2015-11-19 12:11:17 +00:00
'' ;
in
2023-01-01 23:43:00 +00:00
checked result
) ;
2017-08-25 09:47:28 +00:00
2021-03-30 09:31:25 +00:00
# Merge the tarballs of images built with buildImage into a single
# tarball that contains all images. Running `docker load` on the resulting
# tarball will load the images into the docker daemon.
mergeImages = images : runCommand " m e r g e - d o c k e r - i m a g e s "
2021-03-30 07:04:47 +00:00
{
2021-03-30 09:31:25 +00:00
inherit images ;
2024-02-17 16:48:57 +00:00
nativeBuildInputs = [ file jq ]
++ compressors . none . nativeInputs
++ compressors . gz . nativeInputs
++ compressors . zstd . nativeInputs ;
2021-03-30 07:04:47 +00:00
} ''
2021-03-30 09:31:25 +00:00
mkdir image inputs
2021-03-30 07:04:47 +00:00
# Extract images
2021-03-30 17:24:44 +00:00
repos = ( )
manifests = ( )
2024-02-17 16:48:57 +00:00
last_image_mime = " a p p l i c a t i o n / g z i p "
2021-03-30 09:31:25 +00:00
for item in $ images ; do
2021-03-30 17:24:44 +00:00
name = $ ( basename $ item )
mkdir inputs / $ name
2024-02-17 16:48:57 +00:00
last_image_mime = $ ( file - - mime-type - b $ item )
case $ last_image_mime in
" a p p l i c a t i o n / x - t a r " ) $ { compressors . none . decompress } ; ;
" a p p l i c a t i o n / z s t d " ) $ { compressors . zstd . decompress } ; ;
" a p p l i c a t i o n / g z i p " ) $ { compressors . gz . decompress } ; ;
* ) echo " e r r o r : u n e x p e c t e d l a y e r t y p e $ l a s t _ i m a g e _ m i m e " > & 2 ; exit 1 ; ;
esac < $ item | tar - xC inputs / $ name
2021-03-30 17:24:44 +00:00
if [ - f inputs / $ name/repositories ] ; then
repos + = ( inputs / $ name/repositories )
fi
if [ - f inputs / $ name/manifest.json ] ; then
manifests + = ( inputs / $ name/manifest.json )
fi
2021-03-30 09:31:25 +00:00
done
# Copy all layers from input images to output image directory
2023-08-12 15:25:29 +00:00
cp - R - - update = none inputs /* / * i m a g e /
2021-03-30 09:31:25 +00:00
# Merge repositories objects and manifests
2021-03-30 17:24:44 +00:00
jq - s add " ' ' ${ repos [ @ ] } " > repositories
jq - s add " ' ' ${ manifests [ @ ] } " > manifest . json
2021-03-30 09:31:25 +00:00
# Replace output image repositories and manifest with merged versions
2021-03-30 07:04:47 +00:00
mv repositories image/repositories
mv manifest . json image/manifest.json
# Create tarball and gzip
2024-02-17 16:48:57 +00:00
tar - C image - - hard-dereference - - sort = name - - mtime = " @ $ S O U R C E _ D A T E _ E P O C H " - - owner = 0 - - group = 0 - - xform s:' ^ . / ' : : - c . | (
case $ last_image_mime in
" a p p l i c a t i o n / x - t a r " ) $ { compressors . none . compress } ; ;
" a p p l i c a t i o n / z s t d " ) $ { compressors . zstd . compress } ; ;
" a p p l i c a t i o n / g z i p " ) $ { compressors . gz . compress } ; ;
# `*)` not needed; already checked.
esac
) > $ out
2021-03-30 07:04:47 +00:00
'' ;
2020-12-02 11:16:56 +00:00
# Provide a /etc/passwd and /etc/group that contain root and nobody.
# Useful when packaging binaries that insist on using nss to look up
# username/groups (like nginx).
2020-12-02 13:51:06 +00:00
# /bin/sh is fine to not exist, and provided by another shim.
2022-03-24 17:47:30 +00:00
inherit fakeNss ; # alias
2020-12-02 11:16:56 +00:00
2021-08-25 13:22:50 +00:00
# This provides a /usr/bin/env, for shell scripts using the
# "#!/usr/bin/env executable" shebang.
usrBinEnv = runCommand " u s r - b i n - e n v " { } ''
mkdir - p $ out/usr/bin
2022-04-05 00:18:45 +00:00
ln - s $ { coreutils } /bin/env $ out/usr/bin
2021-08-25 13:22:50 +00:00
'' ;
2020-12-02 13:51:06 +00:00
# This provides /bin/sh, pointing to bashInteractive.
2024-01-30 02:29:12 +00:00
# The use of bashInteractive here is intentional to support cases like `docker run -it <image_name>`, so keep these use cases in mind if making any changes to how this works.
2021-08-10 08:24:15 +00:00
binSh = runCommand " b i n - s h " { } ''
2020-12-02 13:51:06 +00:00
mkdir - p $ out/bin
ln - s $ { bashInteractive } /bin/bash $ out/bin/sh
'' ;
2022-04-28 15:56:03 +00:00
# This provides the ca bundle in common locations
caCertificates = runCommand " c a - c e r t i f i c a t e s " { } ''
2022-09-20 01:00:13 +00:00
mkdir - p $ out/etc/ssl/certs $ out/etc/pki/tls/certs
2022-04-28 15:56:03 +00:00
# Old NixOS compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/ssl/certs/ca-bundle.crt
# NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/ssl/certs/ca-certificates.crt
# CentOS/Fedora compatibility.
ln - s $ { cacert } /etc/ssl/certs/ca-bundle.crt $ out/etc/pki/tls/certs/ca-bundle.crt
'' ;
2017-08-25 09:47:28 +00:00
# Build an image and populate its nix database with the provided
# contents. The main purpose is to be able to use nix commands in
# the container.
# Be careful since this doesn't work well with multilayer.
2022-07-01 13:02:00 +00:00
# TODO: add the dependencies of the config json.
2024-05-06 12:57:08 +00:00
buildImageWithNixDb = args : buildImage ( args // { includeNixDB = true ; } ) ;
2020-05-02 14:30:43 +00:00
2024-05-06 12:57:08 +00:00
buildLayeredImageWithNixDb = args : buildLayeredImage ( args // { includeNixDB = true ; } ) ;
2020-05-02 14:30:43 +00:00
2024-02-26 18:41:33 +00:00
# Arguments are documented in ../../../doc/build-helpers/images/dockertools.section.md
2023-01-01 23:43:00 +00:00
streamLayeredImage = lib . makeOverridable (
2021-08-10 08:24:15 +00:00
{
name
2024-02-26 18:41:33 +00:00
, tag ? null
, fromImage ? null
, contents ? [ ]
, config ? { }
, architecture ? defaultArchitecture
, created ? " 1 9 7 0 - 0 1 - 0 1 T 0 0 : 0 0 : 0 1 Z "
, uid ? 0
2024-01-22 11:48:36 +00:00
, gid ? 0
, uname ? " r o o t "
, gname ? " r o o t "
2024-02-26 18:41:33 +00:00
, maxLayers ? 100
, extraCommands ? " "
, fakeRootCommands ? " "
, enableFakechroot ? false
, includeStorePaths ? true
2024-05-03 14:40:48 +00:00
, includeNixDB ? false
2024-02-26 18:41:33 +00:00
, passthru ? { }
2021-08-10 08:24:15 +00:00
,
} :
assert
2020-06-08 09:47:46 +00:00
( lib . assertMsg ( maxLayers > 1 )
2021-08-10 08:24:15 +00:00
" t h e m a x L a y e r s a r g u m e n t o f d o c k e r T o o l s . b u i l d L a y e r e d I m a g e f u n c t i o n m u s t b e g r e a t h e r t h a n 1 ( c u r r e n t v a l u e : ${ toString maxLayers } ) " ) ;
2024-07-17 08:06:42 +00:00
assert
( lib . assertMsg ( enableFakechroot -> ! stdenv . isDarwin ) ''
cannot use ` enableFakechroot ` because ` proot ` is not portable to Darwin . Workarounds :
- use ` fakeRootCommands ` with the restricted ` fakeroot ` environment
- cross-compile your packages
- run your packages in a virtual machine
Discussion : https://github.com/NixOS/nixpkgs/issues/327311'' ) ;
2021-08-10 08:24:15 +00:00
let
baseName = baseNameOf name ;
streamScript = writePython3 " s t r e a m " { } ./stream_layered_image.py ;
baseJson = writeText " ${ baseName } - b a s e . j s o n " ( builtins . toJSON {
2022-11-21 12:10:07 +00:00
inherit config architecture ;
2021-08-10 08:24:15 +00:00
os = " l i n u x " ;
} ) ;
contentsList = if builtins . isList contents then contents else [ contents ] ;
2023-08-16 15:50:10 +00:00
bind-paths = builtins . toString ( builtins . map ( path : " - - b i n d = ${ path } : ${ path } ! " ) [
" / d e v / "
" / p r o c / "
" / s y s / "
" ${ builtins . storeDir } / "
" $ o u t / l a y e r . t a r "
] ) ;
2021-08-10 08:24:15 +00:00
# We store the customisation layer as a tarball, to make sure that
2022-12-18 00:39:44 +00:00
# things like permissions set on 'extraCommands' are not overridden
2021-08-10 08:24:15 +00:00
# by Nix. Then we precompute the sha256 for performance.
customisationLayer = symlinkJoin {
name = " ${ baseName } - c u s t o m i s a t i o n - l a y e r " ;
paths = contentsList ;
2024-05-03 14:40:48 +00:00
extraCommands =
( lib . optionalString includeNixDB ( mkDbExtraCommand contents ) ) + extraCommands ;
inherit fakeRootCommands ;
2021-10-01 13:53:30 +00:00
nativeBuildInputs = [
fakeroot
] ++ optionals enableFakechroot [
2023-08-16 15:50:10 +00:00
proot
2021-10-01 13:53:30 +00:00
] ;
2021-08-10 08:24:15 +00:00
postBuild = ''
mv $ out old_out
( cd old_out ; eval " $ e x t r a C o m m a n d s " )
mkdir $ out
2023-11-19 07:30:20 +00:00
$ { if enableFakechroot then ''
2024-01-23 19:55:08 +00:00
proot - r $ PWD/old_out $ { bind-paths } - - pwd = / fakeroot bash - c '
2023-11-19 07:30:20 +00:00
source $ stdenv/setup
eval " $ f a k e R o o t C o m m a n d s "
tar \
- - sort name \
- - exclude = ./proc \
- - exclude = ./sys \
2023-12-04 03:39:18 +00:00
- - exclude = . ${ builtins . storeDir } \
2023-11-19 07:30:20 +00:00
- - numeric-owner - - mtime " @ $ S O U R C E _ D A T E _ E P O C H " \
- - hard-dereference \
- cf $ out/layer.tar .
'
'' e l s e ''
fakeroot bash - c '
source $ stdenv/setup
cd old_out
eval " $ f a k e R o o t C o m m a n d s "
tar \
- - sort name \
- - numeric-owner - - mtime " @ $ S O U R C E _ D A T E _ E P O C H " \
- - hard-dereference \
- cf $ out/layer.tar .
'
'' }
2021-08-10 08:24:15 +00:00
sha256sum $ out/layer.tar \
| cut - f 1 - d ' ' \
> $ out/checksum
'' ;
} ;
2020-06-08 09:47:46 +00:00
2021-08-10 08:24:15 +00:00
closureRoots = lib . optionals includeStorePaths /* n o r m a l l y t r u e */ (
2021-10-01 11:47:01 +00:00
[ baseJson customisationLayer ]
2021-08-10 08:24:15 +00:00
) ;
overallClosure = writeText " c l o s u r e " ( lib . concatStringsSep " " closureRoots ) ;
# These derivations are only created as implementation details of docker-tools,
# so they'll be excluded from the created images.
2021-10-01 11:47:01 +00:00
unnecessaryDrvs = [ baseJson overallClosure customisationLayer ] ;
2021-08-10 08:24:15 +00:00
conf = runCommand " ${ baseName } - c o n f . j s o n "
{
2024-01-22 11:48:36 +00:00
inherit fromImage maxLayers created uid gid uname gname ;
2021-08-10 08:24:15 +00:00
imageName = lib . toLower name ;
2022-11-18 14:31:53 +00:00
preferLocalBuild = true ;
2021-08-10 08:24:15 +00:00
passthru . imageTag =
if tag != null
then tag
else
2024-03-20 16:54:09 +00:00
lib . head ( lib . strings . splitString " - " ( baseNameOf ( builtins . unsafeDiscardStringContext conf . outPath ) ) ) ;
2021-08-10 08:24:15 +00:00
paths = buildPackages . referencesByPopularity overallClosure ;
nativeBuildInputs = [ jq ] ;
} ''
$ { if ( tag == null ) then ''
outName = " $ ( b a s e n a m e " $ out " ) "
outHash = $ ( echo " $ o u t N a m e " | cut - d - - f 1 )
imageTag = $ outHash
'' e l s e ''
imageTag = " ${ tag } "
'' }
# convert "created" to iso format
if [ [ " $ c r e a t e d " != " n o w " ] ] ; then
created = " $ ( d a t e - I s e c o n d s - d " $ created " ) "
fi
2020-06-08 09:47:46 +00:00
2021-08-10 08:24:15 +00:00
paths ( ) {
cat $ paths $ { lib . concatMapStringsSep " "
( path : " | ( g r e p - v ${ path } | | t r u e ) " )
unnecessaryDrvs }
}
2020-08-14 09:06:00 +00:00
2021-08-10 08:24:15 +00:00
# Compute the number of layers that are already used by a potential
# 'fromImage' as well as the customization layer. Ensure that there is
# still at least one layer available to store the image contents.
usedLayers = 0
2021-03-08 20:36:13 +00:00
2021-08-10 08:24:15 +00:00
# subtract number of base image layers
if [ [ - n " $ f r o m I m a g e " ] ] ; then
( ( usedLayers + = $ ( tar - xOf " $ f r o m I m a g e " manifest . json | jq ' . [ 0 ] . Layers | length' ) ) )
fi
2021-03-08 20:36:13 +00:00
2021-08-10 08:24:15 +00:00
# one layer will be taken up by the customisation layer
( ( usedLayers + = 1 ) )
2021-03-08 20:36:13 +00:00
2021-08-10 08:24:15 +00:00
if ! ( ( $ usedLayers < $ maxLayers ) ) ; then
echo > & 2 " E r r o r : u s e d L a y e r s $ u s e d L a y e r s l a y e r s t o s t o r e ' f r o m I m a g e ' a n d " \
" ' e x t r a C o m m a n d s ' , b u t o n l y m a x L a y e r s = $ m a x L a y e r s w e r e " \
" a l l o w e d . A t l e a s t 1 l a y e r i s r e q u i r e d t o s t o r e c o n t e n t s . "
exit 1
fi
availableLayers = $ ( ( maxLayers - usedLayers ) )
# Create $maxLayers worth of Docker Layers, one layer per store path
# unless there are more paths than $maxLayers. In that case, create
# $maxLayers-1 for the most popular layers, and smush the remainaing
# store paths in to one final layer.
#
# The following code is fiddly w.r.t. ensuring every layer is
# created, and that no paths are missed. If you change the
# following lines, double-check that your code behaves properly
# when the number of layers equals:
# maxLayers-1, maxLayers, and maxLayers+1, 0
2022-10-12 21:46:01 +00:00
paths |
jq - sR '
rtrimstr ( " \n " ) | split ( " \n " )
| ( . [ : $ maxLayers-1 ] | map ( [ . ] ) ) + [ . [ $ maxLayers-1 : ] ]
| map ( select ( length > 0 ) )
2021-08-10 08:24:15 +00:00
' \
2022-10-12 21:46:01 +00:00
- - argjson maxLayers " $ a v a i l a b l e L a y e r s " > store_layers . json
2021-03-08 20:36:13 +00:00
2022-10-12 21:46:01 +00:00
# The index on $store_layers is necessary because the --slurpfile
# automatically reads the file as an array.
2021-08-10 08:24:15 +00:00
cat $ { baseJson } | jq '
. + {
" s t o r e _ d i r " : $ store_dir ,
" f r o m _ i m a g e " : $ from_image ,
2022-10-12 21:46:01 +00:00
" s t o r e _ l a y e r s " : $ store_layers [ 0 ] ,
2021-08-10 08:24:15 +00:00
" c u s t o m i s a t i o n _ l a y e r " , $ customisation_layer ,
" r e p o _ t a g " : $ repo_tag ,
2024-01-22 11:48:36 +00:00
" c r e a t e d " : $ created ,
" u i d " : $ uid ,
" g i d " : $ gid ,
" u n a m e " : $ uname ,
" g n a m e " : $ gname
2021-08-10 08:24:15 +00:00
}
' - - arg store_dir " ${ storeDir } " \
- - argjson from_image $ { if fromImage == null then " n u l l " else " ' \" ${ fromImage } \" ' " } \
2022-10-12 21:46:01 +00:00
- - slurpfile store_layers store_layers . json \
2021-08-10 08:24:15 +00:00
- - arg customisation_layer $ { customisationLayer } \
- - arg repo_tag " $ i m a g e N a m e : $ i m a g e T a g " \
2024-01-22 11:48:36 +00:00
- - arg created " $ c r e a t e d " \
- - arg uid " $ u i d " \
- - arg gid " $ g i d " \
- - arg uname " $ u n a m e " \
- - arg gname " $ g n a m e " |
2021-08-10 08:24:15 +00:00
tee $ out
'' ;
2022-10-12 21:46:01 +00:00
2021-08-10 08:24:15 +00:00
result = runCommand " s t r e a m - ${ baseName } "
{
2024-05-03 14:40:48 +00:00
inherit conf ;
2021-08-10 08:24:15 +00:00
inherit ( conf ) imageName ;
2024-05-03 14:40:48 +00:00
inherit streamScript ;
2022-11-18 14:31:53 +00:00
preferLocalBuild = true ;
2022-01-31 07:10:10 +00:00
passthru = passthru // {
2021-08-10 08:24:15 +00:00
inherit ( conf ) imageTag ;
2024-07-29 17:45:58 +00:00
inherit conf ;
inherit streamScript ;
2021-08-10 08:24:15 +00:00
# Distinguish tarballs and exes at the Nix level so functions that
# take images can know in advance how the image is supposed to be used.
isExe = true ;
} ;
nativeBuildInputs = [ makeWrapper ] ;
} ''
2024-05-03 14:40:48 +00:00
makeWrapper $ streamScript $ out - - add-flags $ conf
2021-08-10 08:24:15 +00:00
'' ;
in
2023-01-01 23:43:00 +00:00
result
) ;
2022-05-09 18:43:52 +00:00
# This function streams a docker image that behaves like a nix-shell for a derivation
2024-07-05 11:14:02 +00:00
# Docs: doc/build-helpers/images/dockertools.section.md
# Tests: nixos/tests/docker-tools-nix-shell.nix
2022-05-09 18:43:52 +00:00
streamNixShellImage =
2024-07-05 11:14:02 +00:00
{ drv
, name ? drv . name + " - e n v "
, tag ? null
, uid ? 1000
, gid ? 1000
, homeDirectory ? " / b u i l d "
, shell ? bashInteractive + " / b i n / b a s h "
, command ? null
, run ? null
2022-05-09 18:43:52 +00:00
} :
assert lib . assertMsg ( ! ( drv . drvAttrs . __structuredAttrs or false ) )
" s t r e a m N i x S h e l l I m a g e : D o e s n o t w o r k w i t h t h e d e r i v a t i o n ${ drv . name } b e c a u s e i t u s e s _ _ s t r u c t u r e d A t t r s " ;
assert lib . assertMsg ( command == null || run == null )
" s t r e a m N i x S h e l l I m a g e : C a n ' t s p e c i f y b o t h c o m m a n d a n d r u n " ;
let
# A binary that calls the command to build the derivation
builder = writeShellScriptBin " b u i l d D e r i v a t i o n " ''
2024-06-29 15:22:57 +00:00
exec $ { lib . escapeShellArg ( valueToString drv . drvAttrs . builder ) } $ { lib . escapeShellArgs ( map valueToString drv . drvAttrs . args ) }
2022-05-09 18:43:52 +00:00
'' ;
staticPath = " ${ dirOf shell } : ${ lib . makeBinPath [ builder ] } " ;
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526
rcfile = writeText " n i x - s h e l l - r c " ''
unset PATH
dontAddDisableDepTrack = 1
# TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506
[ - e $ stdenv/setup ] && source $ stdenv/setup
PATH = $ { staticPath }: " $ P A T H "
SHELL = $ { lib . escapeShellArg shell }
BASH = $ { lib . escapeShellArg shell }
set + e
[ - n " $ P S 1 " - a - z " $ N I X _ S H E L L _ P R E S E R V E _ P R O M P T " ] && PS1 = ' \ n \ [ \ 033 [ 1 ; 3 2 m \ ] [ nix-shell : \ w ] \ $ \ [ \ 033 [ 0 m \ ] '
if [ " $ ( t y p e - t r u n H o o k ) " = function ] ; then
runHook shellHook
fi
unset NIX_ENFORCE_PURITY
shopt - u nullglob
shopt - s execfail
$ { optionalString ( command != null || run != null ) ''
$ { optionalString ( command != null ) command }
$ { optionalString ( run != null ) run }
exit
'' }
'' ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465
sandboxBuildDir = " / b u i l d " ;
2024-07-05 09:58:55 +00:00
drvEnv =
devShellTools . unstructuredDerivationInputEnv { inherit ( drv ) drvAttrs ; }
// devShellTools . derivationOutputEnv { outputList = drv . outputs ; outputMap = drv ; } ;
2022-05-09 18:43:52 +00:00
# Environment variables set in the image
envVars = {
# Root certificates for internet access
SSL_CERT_FILE = " ${ cacert } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
2024-02-29 06:58:55 +00:00
NIX_SSL_CERT_FILE = " ${ cacert } / e t c / s s l / c e r t s / c a - b u n d l e . c r t " ;
2022-05-09 18:43:52 +00:00
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030
# PATH = "/path-not-set";
# Allows calling bash and `buildDerivation` as the Cmd
PATH = staticPath ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038
HOME = homeDirectory ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044
NIX_STORE = storeDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047
# TODO: Make configurable?
NIX_BUILD_CORES = " 1 " ;
} // drvEnv // {
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010
NIX_BUILD_TOP = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013
TMPDIR = sandboxBuildDir ;
TEMPDIR = sandboxBuildDir ;
TMP = sandboxBuildDir ;
TEMP = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019
PWD = sandboxBuildDir ;
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074
# We don't set it here because the output here isn't handled in any special way
# NIX_LOG_FD = "2";
# https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077
TERM = " x t e r m - 2 5 6 c o l o r " ;
} ;
in streamLayeredImage {
inherit name tag ;
contents = [
binSh
usrBinEnv
( fakeNss . override {
# Allows programs to look up the build user's home directory
# https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910
# Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir.
# We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379
extraPasswdLines = [
" n i x b l d : x : ${ toString uid } : ${ toString gid } : B u i l d u s e r : ${ homeDirectory } : / n o s h e l l "
] ;
extraGroupLines = [
" n i x b l d : ! : ${ toString gid } : "
] ;
} )
] ;
fakeRootCommands = ''
2022-11-07 18:37:34 +00:00
# Effectively a single-user installation of Nix, giving the user full
# control over the Nix store. Needed for building the derivation this
# shell is for, but also in case one wants to use Nix inside the
# image
mkdir - p ./nix / { store , var/nix } ./etc/nix
chown - R $ { toString uid }: $ { toString gid } ./nix ./etc/nix
2022-05-09 18:43:52 +00:00
# Gives the user control over the build directory
mkdir - p . ${ sandboxBuildDir }
chown - R $ { toString uid }: $ { toString gid } . ${ sandboxBuildDir }
'' ;
# Run this image as the given uid/gid
config . User = " ${ toString uid } : ${ toString gid } " ;
config . Cmd =
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186
# https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536
if run == null
then [ shell " - - r c f i l e " rcfile ]
else [ shell rcfile ] ;
config . WorkingDir = sandboxBuildDir ;
config . Env = lib . mapAttrsToList ( name : value : " ${ name } = ${ value } " ) envVars ;
} ;
# Wrapper around streamNixShellImage to build an image from the result
2024-07-05 11:14:02 +00:00
# Docs: doc/build-helpers/images/dockertools.section.md
# Tests: nixos/tests/docker-tools-nix-shell.nix
2024-02-17 16:48:57 +00:00
buildNixShellImage = { drv , compressor ? " g z " , . . . } @ args :
2022-05-09 18:43:52 +00:00
let
2024-02-28 11:13:58 +00:00
stream = streamNixShellImage ( builtins . removeAttrs args [ " c o m p r e s s o r " ] ) ;
2024-02-17 16:48:57 +00:00
compress = compressorForImage compressor drv . name ;
2022-05-09 18:43:52 +00:00
in
2024-02-17 16:48:57 +00:00
runCommand " ${ drv . name } - e n v . t a r ${ compress . ext } "
2022-05-09 18:43:52 +00:00
{
inherit ( stream ) imageName ;
passthru = { inherit ( stream ) imageTag ; } ;
2024-02-17 16:48:57 +00:00
nativeBuildInputs = compress . nativeInputs ;
} " ${ stream } | ${ compress . compress } > $ o u t " ;
2015-11-19 12:11:17 +00:00
}