Several processes in one container¶
The rule says one process per container. It is a good rule, and then reality
hands you a workload that does not split: an application and the FastCGI process
it talks to over a Unix socket, a daemon and the agent that ships its logs, a
legacy service that expects cron next to it. Splitting them means exposing an
internal socket on the network, or sharing a volume between two containers just
so they can see the same file.
So people put both processes in one image, and discover what pid 1 is for.
What breaks without an init¶
A shell script starting two daemons is pid 1. It does none of pid 1's job:
- Zombies accumulate. Nothing reaps orphaned children.
- A death goes unnoticed. The secondary process dies, the container stays up because pid 1 is still alive, and the service is silently broken. Your orchestrator sees a healthy container.
- Nothing enforces order. The application starts before the socket it needs
exists, and you paper over it with
sleep 2. - Shutdown is not clean.
docker stopsignals pid 1 only. The children are killed the hard way when the timeout expires. - The logs are one stream. Two daemons interleaved on stdout, with no way to ask for one of them.
The usual answers are supervisord, or tini plus a wrapper script. On Obarun the init you already run on your machines does the job:
66 boot -c makes 66 itself pid 1 of the container.
Getting a Docker daemon on Obarun¶
The rest of this page assumes docker answers. On Obarun the daemon is a 66
service like any other:
dockerd-66serv pulls docker in. -S starts it immediately as well as
enabling it for the next boot. To use docker without being root, add yourself
to the docker group and log in again:
Warning
Membership of the docker group is equivalent to root on the machine. Give
it deliberately.
A worked example: nginx and php-fpm¶
Two processes that genuinely belong together. php-fpm listens on
/run/php-fpm/php-fpm.sock, nginx talks to that socket, and nginx must not
start first.
observice packages both as 66 services, so there is nothing to write:
Declaring the order¶
The shipped nginx service does not know about php-fpm, and that is correct: it
is your deployment that couples them. Copy the frontend into /etc/66/service,
where your own definitions live and win over the distribution's, and add one
line:
That single Depends is the whole ordering problem: 66 starts php-fpm first,
stops it last, and pulls it in automatically when you enable nginx.
The image¶
FROM obarun/base
RUN pacman -Sy --noconfirm --needed nginx-66serv php-fpm-66serv \
&& rm -rf /var/cache/pacman/pkg/*
COPY nginx.66 /etc/66/service/nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY index.php /usr/share/nginx/html/index.php
RUN 66 enable nginx
CMD ["/usr/bin/66", "boot", "-c"]
obarun/base already ships 66 and its unprivileged logger user, so the image is
the base image plus your services. 66 enable nginx pulls php-fpm in through
the dependency.
--cap-add SYS_ADMIN is required, and forgetting it is silent
66 mounts a tmpfs on /run during the boot, which a container cannot do
without that capability.
Forgetting it does not give you a clean failure. The container stays up
and Docker reports it as running, but /run is not a tmpfs, the services
never come up, and nothing answers:
$ docker inspect -f '{{.State.Status}}' nocap
running
$ docker exec nocap 66 status nginx
Status : enabled, down since 12s by self
$ docker logs nocap
sulogin: cannot read /dev/tty: Bad file descriptor
A container that looks healthy and serves nothing is precisely what this
page exists to avoid. Check 66 status after the first boot rather than
trusting the container state.
What that buys you¶
pid 1 is 66¶
$ docker exec my66web ps -o pid,comm
PID COMMAND
1 66-scandir
10 66-supervise
...
16 66-shutdownd
18 66-oneshotd
Orphans are reaped, signals are handled, and every service has a supervisor of its own.
The order is respected, and visible¶
$ docker exec my66web 66 status nginx
Status : enabled, up (pid 28) since 8s by boot
Dependencies : php-fpm-log nginx-log php-fpm
php-fpm gets the lower pid, every time. The two -log entries are the loggers
66 created on its own, one per service.
A death is not silent¶
Kill php-fpm and ask 66 what happened:
$ docker exec my66web 66 status php-fpm
Status : enabled, failed (crash-limit) since 40s by self
$ docker exec my66web 66 log php-fpm
[ERROR] Another FPM instance seems to already listen on /run/php-fpm/php-fpm.sock
[ERROR] FPM initialization failed
66 restarted it, the restart failed on a stale socket the killed master left behind, 66 retried until its crash limit and then said so. That is the point: the container is still running, and you know exactly which service is down and why. Without an init you would have learned it from your users.
Clear the leftover and restart:
$ docker exec my66web sh -c 'rm -f /run/php-fpm/php-fpm.sock /run/php-fpm/php-fpm.pid'
$ docker exec my66web 66 restart php-fpm
start: info: Successfully started service: php-fpm
start: info: Successfully started service: nginx
nginx is restarted too, because it depends on php-fpm. The dependency you declared once is honoured in both directions.
One log stream per service¶
$ docker exec my66web 66 log nginx
2026-09-01 14:13:25.271853264 127.0.0.1 - - [01/Sep/2026:14:13:25 +0000] "GET /index.php HTTP/1.1" 200
Timestamped, per service, and 66 log with no argument interleaves everything
when you do want the whole picture.
A clean stop, with an exit code¶
Do not docker stop. Ask 66 to shut down: it brings the trees down in
dependency order, stops the supervisors, then makes pid 1 exit.
0 on a clean shutdown, 111 if the boot itself failed, so an orchestrator can
tell one from the other. 66 poweroff and 66 reboot do the same clean
shutdown while recording a different intent for an outer supervisor.
It survives a restart¶
$ docker start my66web
$ docker exec my66web 66 status nginx
Status : enabled, up (pid 28) since 8s by boot
by boot, not by user: the services are enabled, so they come back on their
own. Stopping the container puts the system down, it does not undo it.
Beyond this example¶
- Prepare something before booting, such as rendering a service file from
environment variables, with an
ENTRYPOINTthat ends inexec /usr/bin/66 boot -c "$@". Theexecis not optional: without it the shell stays pid 1 and you lose everything above. - Ship a whole service system rather than authoring frontends in the image,
with
66 snapshot. - Build on something other than Obarun. 66 needs only
execlineat run time, so Alpine and Debian work; the 66 repository ships a Dockerfile for each.
Those, the init.conf keys, the boot-group trees and the read-only /var/lib
case are covered by the reference:
Running 66 inside a container, and the
Dockerfiles live in
contributions/docker.