Fossil SCM
Refined the Tcl and Python examples in the new §3.2 of the container doc.
Commit
9baa4423f6b7f7c53c7352f5f751957b727a6c40722a5e3347b4d9c6b8b8d72b
Parent
deb99e22e143fb9…
1 file changed
+85
-32
+85
-32
| --- www/containers.md | ||
| +++ www/containers.md | ||
| @@ -223,47 +223,99 @@ | ||
| 223 | 223 | |
| 224 | 224 | (That command assumes you built the container via “`make container`” and |
| 225 | 225 | are therefore using its versioning scheme.) |
| 226 | 226 | |
| 227 | 227 | Another case where you might need to replace this bare-bones “`run`” |
| 228 | -layer with something more functional is that you’ve installed a [server | |
| 228 | +layer with something more functional is that you’re setting up [email | |
| 229 | +alerts](./alerts.md) and need some way to integrate with the host’s | |
| 230 | +[MTA]. There are a number of alternatives in that linked document, so | |
| 231 | +for the sake of discussion, we’ll say you’ve chosen [Method | |
| 232 | +2](./alerts.md#db), which requires a Tcl interpreter and its SQLite | |
| 233 | +extension to push messages into the outbound email queue DB, presumably | |
| 234 | +bind-mounted into the container. | |
| 235 | + | |
| 236 | +One way to do that is to replace STAGE 2 and 3 in the stock `Dockerfile` | |
| 237 | +with this: | |
| 238 | + | |
| 239 | +``` | |
| 240 | + ## --------------------------------------------------------------------- | |
| 241 | + ## STAGE 2: Pare that back to the bare essentials, plus Tcl. | |
| 242 | + ## --------------------------------------------------------------------- | |
| 243 | + FROM alpine AS run | |
| 244 | + ARG UID=499 | |
| 245 | + ENV PATH "/sbin:/usr/sbin:/bin:/usr/bin" | |
| 246 | + COPY --from=builder /tmp/fossil /bin/ | |
| 247 | + RUN set -x \ | |
| 248 | + && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ | |
| 249 | + && echo "fossil:x:${UID}:fossil" >> /etc/group \ | |
| 250 | + && install -d -m 700 -o fossil -g fossil log museum \ | |
| 251 | + && apk add --no-cache tcl sqlite-tcl | |
| 252 | +``` | |
| 253 | + | |
| 254 | +Build it and test that it works like so: | |
| 255 | + | |
| 256 | +``` | |
| 257 | + $ make container-run && | |
| 258 | + echo 'puts [info patchlevel]' | | |
| 259 | + docker exec -i $(make container-version) tclsh | |
| 260 | + 8.6.12 | |
| 261 | +``` | |
| 262 | + | |
| 263 | +You can remove the installation of `busybox-static` in STAGE 1 since | |
| 264 | +Alpine is already based on BusyBox.(^We can’t do “`FROM busybox`” since | |
| 265 | +we need `apk` in this new second stage. Although this means we end up | |
| 266 | +with back-to-back Alpine stages, it isn’t redundant; the second one | |
| 267 | +starts fresh, allowing us to copy in only what we absolutely need from | |
| 268 | +the first.) You should also remove the `PATH` override in the “RUN” | |
| 269 | +stage, since it’s written for the case where everything is in `/bin`. | |
| 270 | + | |
| 271 | +Another useful case to consider is that you’ve installed a [server | |
| 229 | 272 | extension](./serverext.wiki) and you need an interpreter for that |
| 230 | -script. The advice above won’t work except in the unlikely case that | |
| 273 | +script. The first option above won’t work except in the unlikely case that | |
| 231 | 274 | it’s written in one of the bare-bones script interpreters that BusyBox |
| 232 | 275 | ships.(^BusyBox’s `/bin/sh` is based on the old 4.4BSD Lite Almquist |
| 233 | 276 | shell, implementing little more than what POSIX specified in 1989, plus |
| 234 | 277 | equally stripped-down versions of AWK and `sed`.) |
| 235 | 278 | |
| 236 | -Let’s say the extension is written in Python. You could inject that into | |
| 237 | -the stock container via one of “[distroless]” images. Because this will | |
| 238 | -conflict with the bare-bones “`os`” layer we create, the method is more | |
| 239 | -complicated. Essentially, you replace everything in STAGE 2 and 3 inside | |
| 240 | -the `Dockerfile` with: | |
| 241 | - | |
| 242 | - FROM grc.io/distroless/python3-debian11 AS run | |
| 243 | - ARG UID=499 | |
| 244 | - RUN set -x \ | |
| 245 | - && install -d -m 700 -o fossil -g fossil log museum \ | |
| 246 | - && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ | |
| 247 | - && echo "fossil:x:${UID}:fossil" >> /etc/group | |
| 248 | - COPY --from=builder /tmp/fossil /bin/ | |
| 249 | - | |
| 250 | -Another case is that you’re setting up [email alerts](./alerts.md) and | |
| 251 | -need some way to integrate with the host’s [MTA]. There are a number of | |
| 252 | -alternatives in that linked document, so for the sake of discussion, | |
| 253 | -we’ll say you’ve chosen Method 2, which requires a Tcl interpreter to | |
| 254 | -push messages into the outbound email queue DB, presumably bind-mounted | |
| 255 | -into the container. As of this writing, Google offers no “distroless” | |
| 256 | -container images for Tcl, but you *could* replace the `FROM` line above | |
| 257 | -with: | |
| 258 | - | |
| 259 | - FROM alpine AS run | |
| 260 | - RUN apk add --no-cache tcl | |
| 261 | - | |
| 262 | -Everything else remains the same as in the distroless Python example | |
| 263 | -because even Alpine will conflict with the way we set up core Linux | |
| 264 | -directories like `/etc` and `/tmp` in the absence of any OS image. | |
| 279 | +Let’s say the extension is written in Python. While you could handle it | |
| 280 | +the same way we do with Tcl, because Python is more popular, we have | |
| 281 | +more options. Let’s inject that into the stock container via a suitable | |
| 282 | +“[distroless]” image instead. Because this will conflict with the | |
| 283 | +bare-bones “`os`” layer we create, the method is more complicated: | |
| 284 | + | |
| 285 | +``` | |
| 286 | + ## --------------------------------------------------------------------- | |
| 287 | + ## STAGE 2: Pare that back to the bare essentials, plus Python. | |
| 288 | + ## --------------------------------------------------------------------- | |
| 289 | + FROM cgr.dev/chainguard/python:latest | |
| 290 | + USER root | |
| 291 | + ARG UID=499 | |
| 292 | + ENV PATH "/sbin:/usr/sbin:/bin:/usr/bin" | |
| 293 | + COPY --from=builder /tmp/fossil /bin/ | |
| 294 | + COPY --from=builder /bin/busybox.static /bin/busybox | |
| 295 | + RUN [ "/bin/busybox", "--install", "/bin" ] | |
| 296 | + RUN set -x \ | |
| 297 | + && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ | |
| 298 | + && echo "fossil:x:${UID}:fossil" >> /etc/group \ | |
| 299 | + && install -d -m 700 -o fossil -g fossil log museum | |
| 300 | +``` | |
| 301 | + | |
| 302 | +Build it and test that it works like so: | |
| 303 | + | |
| 304 | +``` | |
| 305 | + $ make container-run && | |
| 306 | + docker exec -i $(make container-version) python --version | |
| 307 | + 3.11.2 | |
| 308 | +``` | |
| 309 | + | |
| 310 | +Relative to the Tcl example, the change from “`alpine`” to Chainguard’s | |
| 311 | +Python image means we have no BusyBox environment to execute the `RUN` | |
| 312 | +command with, so we have to copy the `busybox.static` binary in from | |
| 313 | +STAGE 1 and install it in this new STAGE 2 for the same reason the stock | |
| 314 | +container does.(^This is the main reason we change `USER` temporarily to | |
| 315 | +`root` here.) The compensating bonus is huge: we don’t leave a package | |
| 316 | +manager sitting around inside the image, waiting to be abused. | |
| 265 | 317 | |
| 266 | 318 | Beware that there’s a limit to how much the über-jail nature of |
| 267 | 319 | containers can save you when you go and provide a more capable OS layer |
| 268 | 320 | like this. For instance, you might have enabled Fossil’s [risky TH1 docs |
| 269 | 321 | feature][th1docrisk] along with the Tcl integration feature, which |
| @@ -272,11 +324,12 @@ | ||
| 272 | 324 | The container layer should stop that script from accessing any files out |
| 273 | 325 | on the host that you haven’t explicitly mounted into the container’s |
| 274 | 326 | namespace, but it *can* still make network connections, modify the repo |
| 275 | 327 | DB inside the container, and who knows what else. |
| 276 | 328 | |
| 277 | -[distroless]: https://github.com/GoogleContainerTools/distroless | |
| 329 | +[cgimgs]: https://github.com/chainguard-images/images/tree/main/images | |
| 330 | +[distroless]: https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future | |
| 278 | 331 | [MTA]: https://en.wikipedia.org/wiki/Message_transfer_agent |
| 279 | 332 | [th1docrisk]: https://fossil-scm.org/forum/forumpost/42e0c16544 |
| 280 | 333 | |
| 281 | 334 | |
| 282 | 335 | ### 3.3 <a id="caps"></a>Dropping Unnecessary Capabilities |
| 283 | 336 |
| --- www/containers.md | |
| +++ www/containers.md | |
| @@ -223,47 +223,99 @@ | |
| 223 | |
| 224 | (That command assumes you built the container via “`make container`” and |
| 225 | are therefore using its versioning scheme.) |
| 226 | |
| 227 | Another case where you might need to replace this bare-bones “`run`” |
| 228 | layer with something more functional is that you’ve installed a [server |
| 229 | extension](./serverext.wiki) and you need an interpreter for that |
| 230 | script. The advice above won’t work except in the unlikely case that |
| 231 | it’s written in one of the bare-bones script interpreters that BusyBox |
| 232 | ships.(^BusyBox’s `/bin/sh` is based on the old 4.4BSD Lite Almquist |
| 233 | shell, implementing little more than what POSIX specified in 1989, plus |
| 234 | equally stripped-down versions of AWK and `sed`.) |
| 235 | |
| 236 | Let’s say the extension is written in Python. You could inject that into |
| 237 | the stock container via one of “[distroless]” images. Because this will |
| 238 | conflict with the bare-bones “`os`” layer we create, the method is more |
| 239 | complicated. Essentially, you replace everything in STAGE 2 and 3 inside |
| 240 | the `Dockerfile` with: |
| 241 | |
| 242 | FROM grc.io/distroless/python3-debian11 AS run |
| 243 | ARG UID=499 |
| 244 | RUN set -x \ |
| 245 | && install -d -m 700 -o fossil -g fossil log museum \ |
| 246 | && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ |
| 247 | && echo "fossil:x:${UID}:fossil" >> /etc/group |
| 248 | COPY --from=builder /tmp/fossil /bin/ |
| 249 | |
| 250 | Another case is that you’re setting up [email alerts](./alerts.md) and |
| 251 | need some way to integrate with the host’s [MTA]. There are a number of |
| 252 | alternatives in that linked document, so for the sake of discussion, |
| 253 | we’ll say you’ve chosen Method 2, which requires a Tcl interpreter to |
| 254 | push messages into the outbound email queue DB, presumably bind-mounted |
| 255 | into the container. As of this writing, Google offers no “distroless” |
| 256 | container images for Tcl, but you *could* replace the `FROM` line above |
| 257 | with: |
| 258 | |
| 259 | FROM alpine AS run |
| 260 | RUN apk add --no-cache tcl |
| 261 | |
| 262 | Everything else remains the same as in the distroless Python example |
| 263 | because even Alpine will conflict with the way we set up core Linux |
| 264 | directories like `/etc` and `/tmp` in the absence of any OS image. |
| 265 | |
| 266 | Beware that there’s a limit to how much the über-jail nature of |
| 267 | containers can save you when you go and provide a more capable OS layer |
| 268 | like this. For instance, you might have enabled Fossil’s [risky TH1 docs |
| 269 | feature][th1docrisk] along with the Tcl integration feature, which |
| @@ -272,11 +324,12 @@ | |
| 272 | The container layer should stop that script from accessing any files out |
| 273 | on the host that you haven’t explicitly mounted into the container’s |
| 274 | namespace, but it *can* still make network connections, modify the repo |
| 275 | DB inside the container, and who knows what else. |
| 276 | |
| 277 | [distroless]: https://github.com/GoogleContainerTools/distroless |
| 278 | [MTA]: https://en.wikipedia.org/wiki/Message_transfer_agent |
| 279 | [th1docrisk]: https://fossil-scm.org/forum/forumpost/42e0c16544 |
| 280 | |
| 281 | |
| 282 | ### 3.3 <a id="caps"></a>Dropping Unnecessary Capabilities |
| 283 |
| --- www/containers.md | |
| +++ www/containers.md | |
| @@ -223,47 +223,99 @@ | |
| 223 | |
| 224 | (That command assumes you built the container via “`make container`” and |
| 225 | are therefore using its versioning scheme.) |
| 226 | |
| 227 | Another case where you might need to replace this bare-bones “`run`” |
| 228 | layer with something more functional is that you’re setting up [email |
| 229 | alerts](./alerts.md) and need some way to integrate with the host’s |
| 230 | [MTA]. There are a number of alternatives in that linked document, so |
| 231 | for the sake of discussion, we’ll say you’ve chosen [Method |
| 232 | 2](./alerts.md#db), which requires a Tcl interpreter and its SQLite |
| 233 | extension to push messages into the outbound email queue DB, presumably |
| 234 | bind-mounted into the container. |
| 235 | |
| 236 | One way to do that is to replace STAGE 2 and 3 in the stock `Dockerfile` |
| 237 | with this: |
| 238 | |
| 239 | ``` |
| 240 | ## --------------------------------------------------------------------- |
| 241 | ## STAGE 2: Pare that back to the bare essentials, plus Tcl. |
| 242 | ## --------------------------------------------------------------------- |
| 243 | FROM alpine AS run |
| 244 | ARG UID=499 |
| 245 | ENV PATH "/sbin:/usr/sbin:/bin:/usr/bin" |
| 246 | COPY --from=builder /tmp/fossil /bin/ |
| 247 | RUN set -x \ |
| 248 | && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ |
| 249 | && echo "fossil:x:${UID}:fossil" >> /etc/group \ |
| 250 | && install -d -m 700 -o fossil -g fossil log museum \ |
| 251 | && apk add --no-cache tcl sqlite-tcl |
| 252 | ``` |
| 253 | |
| 254 | Build it and test that it works like so: |
| 255 | |
| 256 | ``` |
| 257 | $ make container-run && |
| 258 | echo 'puts [info patchlevel]' | |
| 259 | docker exec -i $(make container-version) tclsh |
| 260 | 8.6.12 |
| 261 | ``` |
| 262 | |
| 263 | You can remove the installation of `busybox-static` in STAGE 1 since |
| 264 | Alpine is already based on BusyBox.(^We can’t do “`FROM busybox`” since |
| 265 | we need `apk` in this new second stage. Although this means we end up |
| 266 | with back-to-back Alpine stages, it isn’t redundant; the second one |
| 267 | starts fresh, allowing us to copy in only what we absolutely need from |
| 268 | the first.) You should also remove the `PATH` override in the “RUN” |
| 269 | stage, since it’s written for the case where everything is in `/bin`. |
| 270 | |
| 271 | Another useful case to consider is that you’ve installed a [server |
| 272 | extension](./serverext.wiki) and you need an interpreter for that |
| 273 | script. The first option above won’t work except in the unlikely case that |
| 274 | it’s written in one of the bare-bones script interpreters that BusyBox |
| 275 | ships.(^BusyBox’s `/bin/sh` is based on the old 4.4BSD Lite Almquist |
| 276 | shell, implementing little more than what POSIX specified in 1989, plus |
| 277 | equally stripped-down versions of AWK and `sed`.) |
| 278 | |
| 279 | Let’s say the extension is written in Python. While you could handle it |
| 280 | the same way we do with Tcl, because Python is more popular, we have |
| 281 | more options. Let’s inject that into the stock container via a suitable |
| 282 | “[distroless]” image instead. Because this will conflict with the |
| 283 | bare-bones “`os`” layer we create, the method is more complicated: |
| 284 | |
| 285 | ``` |
| 286 | ## --------------------------------------------------------------------- |
| 287 | ## STAGE 2: Pare that back to the bare essentials, plus Python. |
| 288 | ## --------------------------------------------------------------------- |
| 289 | FROM cgr.dev/chainguard/python:latest |
| 290 | USER root |
| 291 | ARG UID=499 |
| 292 | ENV PATH "/sbin:/usr/sbin:/bin:/usr/bin" |
| 293 | COPY --from=builder /tmp/fossil /bin/ |
| 294 | COPY --from=builder /bin/busybox.static /bin/busybox |
| 295 | RUN [ "/bin/busybox", "--install", "/bin" ] |
| 296 | RUN set -x \ |
| 297 | && echo "fossil:x:${UID}:${UID}:User:/museum:/false" >> /etc/passwd \ |
| 298 | && echo "fossil:x:${UID}:fossil" >> /etc/group \ |
| 299 | && install -d -m 700 -o fossil -g fossil log museum |
| 300 | ``` |
| 301 | |
| 302 | Build it and test that it works like so: |
| 303 | |
| 304 | ``` |
| 305 | $ make container-run && |
| 306 | docker exec -i $(make container-version) python --version |
| 307 | 3.11.2 |
| 308 | ``` |
| 309 | |
| 310 | Relative to the Tcl example, the change from “`alpine`” to Chainguard’s |
| 311 | Python image means we have no BusyBox environment to execute the `RUN` |
| 312 | command with, so we have to copy the `busybox.static` binary in from |
| 313 | STAGE 1 and install it in this new STAGE 2 for the same reason the stock |
| 314 | container does.(^This is the main reason we change `USER` temporarily to |
| 315 | `root` here.) The compensating bonus is huge: we don’t leave a package |
| 316 | manager sitting around inside the image, waiting to be abused. |
| 317 | |
| 318 | Beware that there’s a limit to how much the über-jail nature of |
| 319 | containers can save you when you go and provide a more capable OS layer |
| 320 | like this. For instance, you might have enabled Fossil’s [risky TH1 docs |
| 321 | feature][th1docrisk] along with the Tcl integration feature, which |
| @@ -272,11 +324,12 @@ | |
| 324 | The container layer should stop that script from accessing any files out |
| 325 | on the host that you haven’t explicitly mounted into the container’s |
| 326 | namespace, but it *can* still make network connections, modify the repo |
| 327 | DB inside the container, and who knows what else. |
| 328 | |
| 329 | [cgimgs]: https://github.com/chainguard-images/images/tree/main/images |
| 330 | [distroless]: https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future |
| 331 | [MTA]: https://en.wikipedia.org/wiki/Message_transfer_agent |
| 332 | [th1docrisk]: https://fossil-scm.org/forum/forumpost/42e0c16544 |
| 333 | |
| 334 | |
| 335 | ### 3.3 <a id="caps"></a>Dropping Unnecessary Capabilities |
| 336 |