Skip to content

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 stop signals 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:

# pacman -S dockerd-66serv
# 66 enable -S dockerd
# 66 status dockerd

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:

# usermod -aG docker alice

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:

# pacman -S nginx-66serv php-fpm-66serv

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:

[Main]
Type = classic
Description = "nginx daemon"
User = ( root )
Depends = ( php-fpm )

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.

$ docker build -t 66-web .
$ docker run -d --name my66web --cap-add SYS_ADMIN 66-web

--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.

$ docker exec my66web 66 halt
$ docker wait my66web
0

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 ENTRYPOINT that ends in exec /usr/bin/66 boot -c "$@". The exec is 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 execline at 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.