{"id":18445783,"url":"https://github.com/voronenko/docker-s6-overlay-notes","last_synced_at":"2025-04-15T01:16:46.548Z","repository":{"id":145345342,"uuid":"246531536","full_name":"Voronenko/docker-s6-overlay-notes","owner":"Voronenko","description":"Notes on incorporating S6 overlay approach into container logic","archived":false,"fork":false,"pushed_at":"2020-03-26T11:40:16.000Z","size":8,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-15T01:16:29.183Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Voronenko.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-11T09:42:32.000Z","updated_at":"2020-03-26T11:40:19.000Z","dependencies_parsed_at":"2023-07-04T15:37:39.192Z","dependency_job_id":null,"html_url":"https://github.com/Voronenko/docker-s6-overlay-notes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Fdocker-s6-overlay-notes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Fdocker-s6-overlay-notes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Fdocker-s6-overlay-notes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronenko%2Fdocker-s6-overlay-notes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Voronenko","download_url":"https://codeload.github.com/Voronenko/docker-s6-overlay-notes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248986316,"owners_count":21194025,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T07:07:21.209Z","updated_at":"2025-04-15T01:16:46.541Z","avatar_url":"https://github.com/Voronenko.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# s6 overlay\n\ns6 docs:  https://github.com/just-containers/s6-overlay/blob/master/README.md\n\nImportant quotes:\n\n## Init stages\n\nOur overlay init is a properly customized one to run appropriately in containerized environments. This section briefly explains how our stages work but if you want to know how a complete init system should work, please read this article: [How to run s6-svscan as process 1](http://skarnet.org/software/s6/s6-svscan-1.html) by Laurent Bercot.\n\n1. **stage 1**: Its purpose is to prepare the image to enter into the second stage. Among other things, it is responsible for preparing the container environment variables, block the startup of the second stage until `s6` is effectively started, ...\n2. **stage 2**: This is where most of the end-user provided files are mean to be executed:\n  1. Fix ownership and permissions using `/etc/fix-attrs.d`.\n  2. Execute initialization scripts contained in `/etc/cont-init.d`.\n  3. Copy user services (`/etc/services.d`) to the folder where s6 is running its supervision and signal it so that it can properly start supervising them. \n3. **stage 3**: This is the shutdown stage. Its purpose is to clean everything up, stop services and execute finalization scripts contained in `/etc/cont-finish.d`. This is when our init system stops all container processes, first gracefully using `SIGTERM` and then (after `S6_KILL_GRACETIME`) forcibly using `SIGKILL`. And, of course, it reaps all zombies :-).\n\n\n## Usage\n\nThe project is distributed as a standard .tar.gz file, which you extract at the root of your image. Afterwards, set your `ENTRYPOINT` to `/init`\n\nRight now, we recommend using Docker's `ADD` directive instead of running `wget` or `curl` in a `RUN` directive - Docker is able to handle the https URL when you use `ADD`, whereas your base image might not be able to use https, or might not even have `wget` or `curl` installed at all.\n\nFrom there, you have a couple of options:\n\n* Run your service/program as your image's `CMD`\n* Write a service script\n\n### Fixing ownership \u0026 permissions\n\nSometimes it's interesting to fix ownership \u0026 permissions before proceeding because, for example, you have mounted/mapped a host folder inside your container. Our overlay provides a way to tackle this issue using files in `/etc/fix-attrs.d`. This is the pattern format followed by fix-attrs files:\n\n```\npath recurse account fmode dmode\n```\n* `path`: File or dir path.\n* `recurse`: (Set to `true` or `false`) If a folder is found, recurse through all containing files \u0026 folders in it.\n* `account`: Target account. It's possible to default to fallback `uid:gid` if the account isn't found. For example, `nobody,32768:32768` would try to use the `nobody` account first, then fallback to `uid 32768` instead.\nIf, for instance, `daemon` account is `UID=2` and `GID=2`, these are the possible values for `account` field:\n  * `daemon:                UID=2     GID=2`\n  * `daemon,3:4:            UID=2     GID=2`\n  * `2:2,3:4:               UID=2     GID=2`\n  * `daemon:11111,3:4:      UID=11111 GID=2`\n  * `11111:daemon,3:4:      UID=2     GID=11111`\n  * `daemon:daemon,3:4:     UID=2     GID=2`\n  * `daemon:unexisting,3:4: UID=2     GID=4`\n  * `unexisting:daemon,3:4: UID=3     GID=2`\n  * `11111:11111,3:4:       UID=11111 GID=11111`\n* `fmode`: Target file mode. For example, `0644`.\n* `dmode`: Target dir/folder mode. For example, `0755`.\n\nHere you have some working examples:\n\n`/etc/fix-attrs.d/01-mysql-data-dir`:\n```\n/var/lib/mysql true mysql 0600 0700\n```\n`/etc/fix-attrs.d/02-mysql-log-dirs`:\n```\n/var/log/mysql-error-logs true nobody,32768:32768 0644 2700\n/var/log/mysql-general-logs true nobody,32768:32768 0644 2700\n/var/log/mysql-slow-query-logs true nobody,32768:32768 0644 2700\n```\n\n### Executing initialization And/Or finalization tasks\n\nAfter fixing attributes (through `/etc/fix-attrs.d/`) and just before starting user provided services up (through `/etc/services.d`) our overlay will execute all the scripts found in `/etc/cont-init.d`, for example:\n\n[`/etc/cont-init.d/02-confd-onetime`](https://github.com/just-containers/nginx-loadbalancer/blob/master/rootfs/etc/cont-init.d/02-confd-onetime):\n```\n#!/usr/bin/execlineb -P\n\nwith-contenv\ns6-envuidgid nginx\nmultisubstitute\n{\n  import -u -D0 UID\n  import -u -D0 GID\n  import -u CONFD_PREFIX\n  define CONFD_CHECK_CMD \"/usr/sbin/nginx -t -c {{ .src }}\"\n}\nconfd --onetime --prefix=\"${CONFD_PREFIX}\" --tmpl-uid=\"${UID}\" --tmpl-gid=\"${GID}\" --tmpl-src=\"/etc/nginx/nginx.conf.tmpl\" --tmpl-dest=\"/etc/nginx/nginx.conf\" --tmpl-check-cmd=\"${CONFD_CHECK_CMD}\" etcd\n```\n\n### Writing a service script\n\nCreating a supervised service cannot be easier, just create a service directory with the name of your service into `/etc/services.d` and put a `run` file into it, this is the file in which you'll put your long-lived process execution. You're done! If you want to know more about s6 supervision of servicedirs take a look to [`servicedir`](http://skarnet.org/software/s6/servicedir.html) documentation. A simple example would look like this:\n\n`/etc/services.d/myapp/run`:\n```\n#!/usr/bin/execlineb -P\nnginx -g \"daemon off;\"\n```\n\n### Writing an optional finish script\n\nBy default, services created in `/etc/services.d` will automatically restart. If a service should bring the container down, you'll need to write a `finish` script that does that. Here's an example finish script:\n\n`/etc/services.d/myapp/finish`:\n```\n#!/usr/bin/execlineb -S0\n\ns6-svscanctl -t /var/run/s6/services\n```\n\nIt's possible to do more advanced operations - for example, here's a script from @smebberson that only brings down the service when it crashes:\n\n`/etc/services.d/myapp/finish`:\n```\n#!/usr/bin/execlineb -S1\nif { s6-test ${1} -ne 0 }\nif { s6-test ${1} -ne 256 }\n\ns6-svscanctl -t /var/run/s6/services\n```\n\n### Logging\n\nOur overlay provides a way to handle logging easily since `s6` already provides logging mechanisms out-of-the-box via [`s6-log`](http://skarnet.org/software/s6/s6-log.html)!. We also provide a helper utility called `logutil-service` to make logging a matter of calling one binary. This helper does the following things:\n- read how s6-log should proceed reading the logging script contained in `S6_LOGGING_SCRIPT`\n- drop privileges to the `nobody` user (defaulting to `32768:32768` if it doesn't exist)\n- clean all the environments variables\n- initiate logging by executing s6-log :-)\n\nThis example will send all the log lines present in stdin (following the rules described in `S6_LOGGING_SCRIPT`) to `/var/log/myapp`: \n\n`/etc/services.d/myapp/log/run`:\n```\n#!/bin/sh\nexec logutil-service /var/log/myapp\n```\n\nIf, for instance, you want to use a fifo instead of stdin as an input, write your log services as follows:\n\n`/etc/services.d/myapp/log/run`:\n```\n#!/bin/sh\nexec logutil-service -f /var/run/myfifo /var/log/myapp\n```\n\n### Dropping privileges\n\nWhen it comes to executing a service, no matter it's a service or a logging service, a very good practice is to drop privileges before executing it. `s6` already includes utilities to do exactly these kind of things:\n\nIn `execline`:\n\n```\n#!/usr/bin/execlineb -P\ns6-setuidgid daemon\nmyservice\n```\n\nIn `sh`:\n\n```\n#!/bin/sh\nexec s6-setuidgid daemon myservice\n```\n\nIf you want to know more about these utilities, please take a look to: [`s6-setuidgid`](http://skarnet.org/software/s6/s6-setuidgid.html), [`s6-envuidgid`](http://skarnet.org/software/s6/s6-envuidgid.html) and [`s6-applyuidgid`](http://skarnet.org/software/s6/s6-applyuidgid.html).\n\n### Container environment\n\nIf you want your custom script to have container environments available just make use of `with-contenv` helper, which will push all of those into your execution environment, for example:\n\n`/etc/cont-init.d/01-contenv-example`:\n```\n#!/usr/bin/with-contenv sh\necho $MYENV\n```\n\nThis script will output whatever the `MYENV` enviroment variable contains.\n\n### Read-Only Root Filesystem\n\nRecent versions of Docker allow running containers with a read-only root filesystem. During init stage 2, the overlay modifies permissions for user-provided files in `cont-init.d`, etc. If the root filesystem is read-only, you can set `S6_READ_ONLY_ROOT=1` to inform stage 2 that it should first copy user-provided files to its work area in `/var/run/s6` before attempting to change permissions. \n\nThis of course assumes that at least `/var` is backed by a writeable filesystem with execute privileges. This could be done with a `tmpfs` filesystem as follows:\n\n```\ndocker run -e S6_READ_ONLY_ROOT=1 --read-only --tmpfs /var:rw,exec [image name]\n```\n\n**NOTE**: When using `S6_READ_ONLY_ROOT=1` you should _avoid using symbolic links_ in `fix-attrs.d`, `cont-init.d`, `cont-finish.d`, and `services.d`. Due to limitations of `s6`, symbolic links will be followed when these directories are copied to `/var/run/s6`, resulting in unexpected duplication.\n\n### Customizing `s6` behaviour\n\nIt is possible somehow to tweak `s6` behaviour by providing an already predefined set of environment variables to the execution context:\n\n* `S6_KEEP_ENV` (default = 0): if set, then environment is not reset and whole supervision tree sees original set of env vars. It switches `with-contenv` into noop.\n* `S6_LOGGING` (default = 0): \n  * **`0`**: Outputs everything to stdout/stderr.\n  * **`1`**: Uses an internal `catch-all` logger and persists everything on it, it is located in `/var/log/s6-uncaught-logs`. Anything run as a `CMD` is still output to stdout/stderr.\n  * **`2`**: Uses an internal `catch-all` logger and persists everything on it, including the output of `CMD`. Absolutely nothing is written to stdout/stderr.\n* `S6_BEHAVIOUR_IF_STAGE2_FAILS` (default = 0):\n  * **`0`**: Continue silently even if any script (`fix-attrs` or `cont-init`) has failed.\n  * **`1`**: Continue but warn with an annoying error message.\n  * **`2`**: Stop by sending a termination signal to the supervision tree.\n* `S6_KILL_FINISH_MAXTIME` (default = 5000): The maximum time (in milliseconds) a script in `/etc/cont-finish.d` could take before sending a `KILL` signal to it. Take into account that this parameter will be used per each script execution, it's not a max time for the whole set of scripts.\n* `S6_SERVICES_GRACETIME` (default = 3000): How long (in milliseconds) `s6` should wait services before sending a `TERM` signal.\n* `S6_KILL_GRACETIME` (default = 3000): How long (in milliseconds) `s6` should wait to reap zombies before sending a `KILL` signal.\n* `S6_LOGGING_SCRIPT` (default = \"n20 s1000000 T\"): This env decides what to log and how, by default every line will prepend with ISO8601, rotated when the current logging file reaches 1mb and archived, at most, with 20 files.\n* `S6_CMD_ARG0` (default = not set): Value of this env var will be prepended to any `CMD` args passed by docker. Use it if you are migrting an existing image to a s6-overlay and want to make it a drop-in replacement, then setting this variable to a value of previously used ENTRYPOINT will improve compatibility with the way image is used.\n* `S6_FIX_ATTRS_HIDDEN` (default = 0): Controls how `fix-attrs.d` scripts process files and directories.\n  * **`0`**: Hidden files and directories are excluded.\n  * **`1`**: All files and directories are processed.\n* `S6_CMD_WAIT_FOR_SERVICES` (default = 0): In order to proceed executing CMD overlay will wait until services are up. Be aware that up doesn't mean ready. Depending if `notification-fd` was found inside the servicedir overlay will use `s6-svwait -U` or `s6-svwait -u` as the waiting statement.\n* `S6_CMD_WAIT_FOR_SERVICES_MAXTIME` (default = 5000): The maximum time (in milliseconds) the services could take to bring up before proceding to CMD executing.\n* `S6_READ_ONLY_ROOT` (default = 0): When running in a container whose root filesystem is read-only, set this env to **1** to inform init stage 2 that it should copy user-provided initialization scripts from `/etc` to `/var/run/s6/etc` before it attempts to change permissions, etc. See [Read-Only Root Filesystem](#read-only-root-filesystem) for more information.\n* `S6_SYNC_DISKS` (default = 0): Set this env to **1** to inform init stage 3 that it should attempt to sync filesystems before stopping the container. Note: this will likely sync all filesystems on the host.\n\n\n## wait utility \n\nhttps://github.com/ufoscout/docker-compose-wait\n\n\n```\n## Add the wait script to the image\nADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /usr/bin/wait\nRUN chmod +x /usr/bin/wait\n\n## Launch the wait tool and then your application\nCMD /wait \u0026\u0026 /MySuperApp.sh\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronenko%2Fdocker-s6-overlay-notes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoronenko%2Fdocker-s6-overlay-notes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronenko%2Fdocker-s6-overlay-notes/lists"}