GNU bug report logs - #76081
OCI provisioning service

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: guix-patches; Reported by: paul <goodoldpaul@HIDDEN>; Keywords: moreinfo; dated Wed, 5 Feb 2025 22:01:03 UTC; Maintainer for guix-patches is guix-patches@HIDDEN.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 17 May 2025 15:26:20 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat May 17 11:26:19 2025
Received: from localhost ([127.0.0.1]:49950 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uGJQX-0005aI-J5
	for submit <at> debbugs.gnu.org; Sat, 17 May 2025 11:26:19 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:43267)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uGJQU-0005Zn-JY
 for 76081 <at> debbugs.gnu.org; Sat, 17 May 2025 11:26:15 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1747495571;
 bh=jj6vK3YpZuK1h00KwDYBxr/EvLswRilJ3xzPacJRlM4=;
 h=Date:Subject:To:Cc:References:From:In-Reply-To:From;
 b=sgY6ym/WmoCSEM9aKKK89uPSgJ+Q6PdhwGXoeXQQcH2jnGXP15+3pjAFt4FdnLvNu
 11vXGc10uWgY4KxVS14kOXI/Phly7/3wl/2wqEbO0iF0idDoujoa1VerssiHdrmPn9
 9nC3Ubp64uuXbuxJ2/2WAACrgPw5RKxPOy3wexN4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4b07835H4vz11K3;
 Sat, 17 May 2025 15:26:11 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4b07834M3xz11Jy; Sat, 17 May 2025 15:26:11 +0000 (UTC)
Content-Type: multipart/alternative;
 boundary="------------wCzqJ005QMUYGaIum4x04eiS"
Message-ID: <edfa6206-2784-40cf-92b4-cbe28cb42282@HIDDEN>
Date: Sat, 17 May 2025 17:26:11 +0200
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: [bug#76081] [PATCH v10 3/7] tests: oci-container: Set explicit
 timeouts.
To: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <b129e5ea7e6d09888d784f4622c3ad6690e6e8eb.1746431874.git.goodoldpaul@HIDDEN>
 <87r00qibjd.fsf@HIDDEN>
Content-Language: en-US
From: paul <goodoldpaul@HIDDEN>
In-Reply-To: <87r00qibjd.fsf@HIDDEN>
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This is a multi-part message in MIME format.
--------------wCzqJ005QMUYGaIum4x04eiS
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit

Hi Maxim,

thank you for your feedback, I should have addressed all your comments.

On 5/15/25 09:21, Maxim Cournoyer wrote:
>> +              (let loop ((attempts 0))
>> +                (if (= attempts 60)
>> +                    (error "Service didn't come up after more than 60 seconds")
> I'm curious as to why this is necessary, given in the previous test we
> wait for the docker-guile service to be ready?
>
I think it is because between once the docker run command is run and the 
container is actually started and available through docker ps, there 
seems to be a delay. If the system is not overloaded it is minimal, but 
it happened to me that similarly to what happened in issue 72740 
sometimes container are slow to start.

I think the core problem is that the Shepherd services goes to early in 
the running state, I should make it stay in starting state until the 
container is actually started but I don't know how. If you have any 
pointers on how to achieve this I would be very happy to learn more. I 
would address it in a separate issue if you agree.

It happened to me that running the test suite while compiling other guix 
packages could make the test fail, so I think if we keep this patch we 
could be safer against flaky tests. What do you think?

thank you,


cheers,

giacomo

--------------wCzqJ005QMUYGaIum4x04eiS
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hi Maxim,</p>
    <p>thank you for your feedback, I should have addressed all your
      comments.<br>
    </p>
    <div class="moz-cite-prefix">On 5/15/25 09:21, Maxim Cournoyer
      wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:87r00qibjd.fsf@HIDDEN"><span
      style="white-space: pre-wrap">
</span>
      <blockquote type="cite">
        <pre class="moz-quote-pre" wrap="">+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
</pre>
      </blockquote>
      <pre class="moz-quote-pre" wrap="">
I'm curious as to why this is necessary, given in the previous test we
wait for the docker-guile service to be ready?

</pre>
    </blockquote>
    <p>I think it is because between once the docker run command is run
      and the container is actually started and available through docker
      ps, there seems to be a delay. If the system is not overloaded it
      is minimal, but it happened to me that similarly to what happened
      in issue 72740 sometimes container are slow to start.</p>
    <p>I think the core problem is that the Shepherd services goes to
      early in the running state, I should make it stay in starting
      state until the container is actually started but I don't know
      how. If you have any pointers on how to achieve this I would be
      very happy to learn more. I would address it in a separate issue
      if you agree.<br>
    </p>
    <p>It happened to me that running the test suite while compiling
      other guix packages could make the test fail, so I think if we
      keep this patch we could be safer against flaky tests. What do you
      think?<br>
      <br>
      thank you,</p>
    <p><br>
    </p>
    <p>cheers,</p>
    <p>giacomo<br>
    </p>
  </body>
</html>

--------------wCzqJ005QMUYGaIum4x04eiS--




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.
Added tag(s) moreinfo. Request was from Maxim Cournoyer <maxim.cournoyer@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 16 May 2025 02:07:59 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 22:07:59 2025
Received: from localhost ([127.0.0.1]:58941 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFkUR-0007TT-5B
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 22:07:59 -0400
Received: from mail-pj1-x102b.google.com ([2607:f8b0:4864:20::102b]:44298)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFkUO-0007Sx-Ft
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 22:07:57 -0400
Received: by mail-pj1-x102b.google.com with SMTP id
 98e67ed59e1d1-30e7bfef364so544828a91.1
 for <76081 <at> debbugs.gnu.org>; Thu, 15 May 2025 19:07:56 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747361270; x=1747966070; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=9tiwGERraBsFgh4U2iphY7hbNznCcQJSW43dMULMFOA=;
 b=Yc2bj8aIOjfxuLaVu8WAhetuTU9lCpWaGvLQFKDFy5tsiM0MmR/H8B/iLSPi3Z5xr2
 5jCSpgQRi/Qt7nzrfL1LR3EaxvoYzTdZoCYpolpyj4P/QLVxTB7VrRD7YXyCGd0tEgvD
 98w7Y62zPnhjqZh5a3IYFaFkWjjjmXuMm/Q5kvJibrA5AkeUVWqhKR+8UGT4Kv++iDO2
 E4I8QmVrJaZFyUwx3KjbOPRyBdqmLd7mIPQnD3pIzKLks5FRmoCWVXPeE0phQ8nbmZuy
 2Euyk0iC5+6NX4fmosM2dkn6DPg8e7ALF+p2c8AvfTV4u7wU68bV7HPf3Dxznzj4D2J9
 jr2Q==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747361270; x=1747966070;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=9tiwGERraBsFgh4U2iphY7hbNznCcQJSW43dMULMFOA=;
 b=AL9oqXWjIj0MM+i0CLMOSKqS7yO7iCKG1BTO4RhMbfOh9Q3r0LlBqpEdMAnX1KLoiH
 5WAFTw0iQK6N8jxJtSSKblgu8Bcg/GRPdWdqVxcAwBdOd9CwNZ4bWY3OM1w8taA/DkLd
 5tH15TnISKTmJAeWgjjKlnGsPlvXkj8+CFGWu5cuOc0v075H+Y1pWFanSqjjUh6j5/Xb
 fMlF8i9uEWUb973EqYT2Q2ItR7g2W2ZRsJO8CKRzFz07qhEC28fFrQui2COQ/Xei7bVe
 ohauJIHWfUnFHsai5rYxVxlRfhXWwDDdArdhMp4fub+CUBKn3qzQESEcXx3wYjhSY8yi
 YucA==
X-Gm-Message-State: AOJu0YyeSUmyGrT32+UMn2IacEfAESv8NG348GQCYCAavIys0c/mQL7l
 5SKfvzN3rNNto5Uwpmqlki2J6BO/FknjzpEK8gO+fPFYZVgcRnEiX063
X-Gm-Gg: ASbGncupYUICwNy30AhrocCMahf+aS6dHTqahWBpYvTW6o2iZtW9Gaxqldi6RDyfeLn
 uao6ORtwBPgRXHmo7QTmbZ2o/+8TsI5YDdZdgEdQ/3Y8f9itQieuyT/JGyeIfUo4bRoOlkrCz10
 BM1KFxUziV42uznkzFSPR4AAcjcYu3QZlnAFAcKnON54X5hRL8iEA8IIcMiAmRHuyvuWCKa12eH
 fGdQk7FMo9y0rT8+0Q9NpZzgQIxr4f3cBK+tjrRKnCGPMzoz6woYBD8S6LIu7eggE061VcsKuSj
 skhz3C/3sTar3UBO8bYjn8xJ4EmceHM/O3J8+uk0P21KIGz0bQ==
X-Google-Smtp-Source: AGHT+IHImlXFfJbZQ7FyIM7SHnBVlFs8+YxjDPT4Cu4n5v1BbI7u7FR5/RwaKj0zc6+TA/+pk9h/MQ==
X-Received: by 2002:a17:90b:33c2:b0:30e:6a9d:d787 with SMTP id
 98e67ed59e1d1-30e7d50b04bmr2267950a91.11.1747361270391; 
 Thu, 15 May 2025 19:07:50 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 98e67ed59e1d1-30e334fb829sm4202414a91.45.2025.05.15.19.07.47
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Thu, 15 May 2025 19:07:49 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 7/7] home: Add home-oci-service-type.
In-Reply-To: <91c9920430684861541c86a01c28bd46423102c8.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:54 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <91c9920430684861541c86a01c28bd46423102c8.1746431874.git.goodoldpaul@HIDDEN>
Date: Fri, 16 May 2025 11:07:46 +0900
Message-ID: <877c2hi9zh.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Tanguy Le Carrour <tanguy@HIDDEN>,
 Ludovic =?utf-8?Q?Court=C3=A8s?= <ludo@HIDDEN>,
 Gabriel Wicki <gabriel@HIDDEN>, Andrew Tropin <andrew@HIDDEN>,
 Hilton Chain <hako@HIDDEN>, 76081 <at> debbugs.gnu.org,
 Janneke Nieuwenhuizen <janneke@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> * gnu/home/service/containers.scm: New file;
> * gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
> * doc/guix.texi (OCI backed services): Document it.

LGTM, but please break long lines so they fit in 80 columns.  This
shouldn't be merged until 6/7 has been understood; if it can be
eliminated this will need to be adjusted like:

--8<---------------cut here---------------start------------->8---
modified   gnu/home/services/containers.scm
@@ -32,16 +32,14 @@ (define home-oci-service-type
                 (extensions
                  (list
                   (service-extension home-profile-service-type
-                                     (oci-service-extension-wrap-validate
-                                      (lambda (config)
-                                        (let ((runtime-cli
-                                               (oci-configuration-runtime-cli config))
-                                              (runtime
-                                               (oci-configuration-runtime config)))
-                                          (oci-service-profile runtime runtime-cli)))))
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
                   (service-extension home-shepherd-service-type
-                                     (oci-service-extension-wrap-validate
-                                      oci-configuration->shepherd-services))))
+                                     oci-configuration->shepherd-services)))
                 (extend
                  (lambda (config extension)
                    (for-home
--8<---------------cut here---------------end--------------->8---

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 16 May 2025 02:02:47 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 22:02:46 2025
Received: from localhost ([127.0.0.1]:58907 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFkPO-000701-IN
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 22:02:46 -0400
Received: from mail-pf1-x42f.google.com ([2607:f8b0:4864:20::42f]:42209)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFkPL-0006zl-UR
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 22:02:44 -0400
Received: by mail-pf1-x42f.google.com with SMTP id
 d2e1a72fcca58-7399838db7fso1627087b3a.0
 for <76081 <at> debbugs.gnu.org>; Thu, 15 May 2025 19:02:43 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747360957; x=1747965757; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=xQjHNTMcFaYt/NCEw1pjhGl+1VV5HnClwhqGiffEihk=;
 b=SivOC7JgsAI8uP/y3Nxn8WMiHs4bS8ZWn1R7UinYJbx+302+R8bTNDCeA5PqPrKR06
 0qz6fj98hh721jj2VcAJqrCZlb1kqUePy3+qZPpcig/8t4QiOW+wwpTRiYWXVoW7ihHj
 K2orsNAY7Qm7rbV++yReWmxaOP4mj1pfihkQyij6O8n+3+gbdrzd3R6IRbv55P6D5nfW
 fq10HL/wwhW2j9P6u/4J3MrkObPtt9WVaelbOj2HxfH/VY+7o8EMEnvG1t9KWeIGZw7/
 iJtghGFzgVi/ZlaDb6iY/PwanyayQDHCPX43rHn9onhJM9LctnluhxMK4EjXiGQ1Yfuo
 eWQg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747360957; x=1747965757;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=xQjHNTMcFaYt/NCEw1pjhGl+1VV5HnClwhqGiffEihk=;
 b=CPQFB9i4L2kxgCFBKFCD+nTXNHqCA98rLQWxtm+j352d2hw7u8mKFbT/KeLgHOJZUZ
 D1jvtxM6Af9fdHd3PjKFb2W7sRi58rsLMpbGCX1TtjmGM7/Q9zqaoy/Ps1uiqXc2X+fH
 ypF8kbrvqE4hK5tWWVsUVDom5x8X0Pqg5KbDjfTsCz7WGyNo3m19gZ93VvH+GmfTg8+H
 fMFYipSx+bSpH/5Sq5ChfLfI+O5ITbxdRcPqZitjwx8HZxdFidAleD+zA4Co648KsEpV
 5T7iCnBx2f8+s1lB4pXA6A8mDukPFtyzWFaUYat6HfiIKvJrmHBOlBc1heFKCPrX2dI4
 Ey8w==
X-Gm-Message-State: AOJu0YxqRT9Bvzjjymf844sBCapFQqgnmIqPXF/wPEpHPd5uMzUdy4me
 bBRaKPyhma/41aVgpnb6v5CKG8ag93bxbtOaISnUTO19ud8bA0Bo3y1c6fCRKg==
X-Gm-Gg: ASbGncsdOW1Uf9jkZZrMYSNjEKKQWHACMEwYYYg9iF4mBYdSSHY/AWJEG1/koMVa8lE
 MmfzQocTQ/PO3xP8P+fHy2QEee1/X0VdO84sy6EcxoE6FcoTA0vp+HRWL+tTyfdIzfXk6btT+79
 e5HyHt7lBgiDwXs1tXm/ZIFp2wB1wx148WJgtnv4Mksm9696wRKpHbIn2yvftWU/1MnssoExdEz
 pDAt+b5Ky45Amv4CWxnE5ZPfC4/TGm4d12Fa9hmC2XzNHDdlhkE34dua7tTj1lAAduV3MoViR1S
 t9JkxUl1bzosYGkSzKwKbdVz2TFQ2QxZu0Y0vnzmql6CvY+JtpKt+Z3Ch5pJ
X-Google-Smtp-Source: AGHT+IG7SvR9+43q0w3tD4Jc6Qzufyck2aylhnwKaz1NIg3JV8rkEIllg4ctiaSXGnqawgEbXVjBYw==
X-Received: by 2002:aa7:91d3:0:b0:736:4c3d:2cba with SMTP id
 d2e1a72fcca58-7429625dd7fmr6578658b3a.9.1747360957172; 
 Thu, 15 May 2025 19:02:37 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-742a970b9basm482286b3a.49.2025.05.15.19.02.35
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Thu, 15 May 2025 19:02:36 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 6/7] services: oci: Migrate
 oci-configuration to (guix records).
In-Reply-To: <1f148e2994e78ed4614efe885380c06740515d0b.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:53 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <1f148e2994e78ed4614efe885380c06740515d0b.1746431874.git.goodoldpaul@HIDDEN>
Date: Fri, 16 May 2025 11:02:34 +0900
Message-ID: <87bjrtia85.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> This commit migrates oci-configuration to (guix records) singe it
> appears (for-home (oci-configuration ...)) does not work as expected
> with (gnu services configuration).  This is supposed to be completely
> transparent for users and can be reverted in the
> future once this has been implemented.
>
> * gnu/service/containers.scm: Migrate oci-configuration to (guix records).

I'd like to understand and resolve this issue and avoid pushing this
commit.  What is the errors you are seeing?  It at least byte-compiles
fine with this commit reverted and the following diff applied:

--8<---------------cut here---------------start------------->8---
modified   gnu/home/services/containers.scm
@@ -32,16 +32,14 @@ (define home-oci-service-type
                 (extensions
                  (list
                   (service-extension home-profile-service-type
-                                     (oci-service-extension-wrap-validate
-                                      (lambda (config)
-                                        (let ((runtime-cli
-                                               (oci-configuration-runtime-cli config))
-                                              (runtime
-                                               (oci-configuration-runtime config)))
-                                          (oci-service-profile runtime runtime-cli)))))
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
                   (service-extension home-shepherd-service-type
-                                     (oci-service-extension-wrap-validate
-                                      oci-configuration->shepherd-services))))
+                                     oci-configuration->shepherd-services)))
                 (extend
                  (lambda (config extension)
                    (for-home
--8<---------------cut here---------------end--------------->8---

Also, there are other home services using a define-configuration'd
record with for-home, such as the home-snuik-service-type, so it should
be possible to make it work.

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 16 May 2025 01:45:49 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 21:45:49 2025
Received: from localhost ([127.0.0.1]:58789 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFk8x-0005c6-Mh
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 21:45:49 -0400
Received: from mail-pj1-x1030.google.com ([2607:f8b0:4864:20::1030]:53319)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFk8r-0005bP-NM
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 21:45:42 -0400
Received: by mail-pj1-x1030.google.com with SMTP id
 98e67ed59e1d1-30e57a37294so1268185a91.2
 for <76081 <at> debbugs.gnu.org>; Thu, 15 May 2025 18:45:41 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747359935; x=1747964735; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=BKL9lu8Wzo0gStJWF+UtRA9qA5IKmVn6Cy0zLcoFTnY=;
 b=NxtmDajc89nlovR9S3qn/pS6roPeSNGsJ6R5HTe27Bedj7inw2PV529Hf1Xr5CE5Hh
 ZI6OUjSm+C9oLd1Ke9mF4nwEfRBBeh1tASUTcXLuHXJTQyEixOAbz8SwIgFOS1ITLaGh
 Y5lLt4+bfpDy7r8iT870a50+cLP/GMRDIvAD4BBSNVaE3m+djJjZKMMCUkW9lGpSgyLA
 oV+uJJNgOf5nNHzcJdLk8EsPnfdisztTyVskQD8X49PE56Hh3XZl8E5rsPhfCJv2V+kW
 /oWncY+aREy7gy0NzjwXGrR0k/HDup0cR80FtDu93VFkx2jlMmAxhdiz3lP6n18DFsRn
 JZcw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747359935; x=1747964735;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=BKL9lu8Wzo0gStJWF+UtRA9qA5IKmVn6Cy0zLcoFTnY=;
 b=hSK+mH3/mlU9VOfKVq/JPhUUcnzB7zR5GrV2ldfZ+/HFgWLGBy46uRzeg5IBEBaOL8
 vVnfwnct/q85fRBk9jVgDAjF9gSguWCDdaXNrPeTydFWEGx4qOwv+HYNBoM/4ZBkpeS/
 /+IhfLy06pCBgfIfyvDJl0Kxk52aW2CYcph+kZJUmjb3SNKqF7eyW/Z9FMdZtGoVeKtd
 uYqbZtWGEJ8VEBQWaceim5GG5Ta59tOrljaJw1JVS+QG+11sfQbyQO0ExvmDTQ3HnOcc
 wyOCkQQ2Ci8exODxmx7Jo4DngOsQsExU7iVZ+enynVGyRbN/ivICp91IxeYlucZ5aSz0
 bMwQ==
X-Gm-Message-State: AOJu0YxXn3+P8iqOhfwtUOJFBPd/B0CmYzs7d1M7ji2sXYRYx0AhNIBj
 nPf7D/3KMV88dIlLSxmbw79KhWSFHFNwWLeQXrwsqA9NXScIt0UZLtomCeRB1Q==
X-Gm-Gg: ASbGnctnQeA/i0sfYxN3KE0wK7Y+zdUrpCx2kp/5gOvSD6ZelQGbxwbLadTV6RcMGj/
 SzoXOb9XixJsfRBd8vQiZy2zEREPB3kf2oJHCNGJD7UIOKPyNuf0jC7WE0BV2caB+4qtbo5wRkX
 nNi2+kN7mK6EbCW151UTZ4hmxw0wb1U1iK6dzReMkewfjw4iiQZQgxaQ0BBZ5D8VjwQzNW2iN2+
 VFWv9oRKiErPTVsl6XbkR1U6X181uTObGzoIdoigQzKsHG+4UButMNtAhpJrkc9R7LoE/hcWgub
 hv0DMn2bMHAP9q0VWHEbG4w/thtfaWze3yFtRry9PaD2Utpz9w==
X-Google-Smtp-Source: AGHT+IG7PQ8vnlYLQxJ3dLTJBfoVA6866NtEVyXNGxTYrikGe4Sy+JUja8w0zTgG+btTaxO4kV8P1Q==
X-Received: by 2002:a17:90b:35ca:b0:2ee:f440:53ed with SMTP id
 98e67ed59e1d1-30e83228de0mr903715a91.31.1747359934901; 
 Thu, 15 May 2025 18:45:34 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 98e67ed59e1d1-30e7d46ef5bsm491939a91.1.2025.05.15.18.45.33
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Thu, 15 May 2025 18:45:34 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 5/7] tests: Use lower-oci-image-state in
 container tests.
In-Reply-To: <cd5b10a3f3ecb770539718e8d6db11362087d73d.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:52 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <cd5b10a3f3ecb770539718e8d6db11362087d73d.1746431874.git.goodoldpaul@HIDDEN>
Date: Fri, 16 May 2025 10:45:32 +0900
Message-ID: <87frh5ib0j.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> This patch replaces boilerplate in container related tests with
> oci-image plumbing from (gnu services containers).
>
> * gnu/services/containers.scm: Export lower-oci-image-state.

I don't think that's a nice API to export; you can instead use the

(define lower-oci-image-state
  (@@ (gnu services containers) lower-oci-image-state)

trick to expose internals (white box testing).

> * gnu/tests/containers.scm (%oci-tarball): New variable;
> (run-rootless-podman-test): use %oci-tarball;
> (build-tarball&run-rootless-podman-test): drop procedure.
> * gnu/tests/docker.scm (%docker-tarball): New variable;
> (build-tarball&run-docker-test): use %docker-tarball;
> (%docker-system-tarball): New variable;
> (build-tarball&run-docker-system-test): new procedure.
>
> Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
> ---
>  gnu/services/containers.scm |   2 +
>  gnu/tests/containers.scm    |  80 ++++++++++++++---------------
>  gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
>  3 files changed, 95 insertions(+), 87 deletions(-)

The diffstats suggest the result is not that impactful in terms of
reducing boilerplate, ah!

> diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
> index 66b46456800..a8d10d842da 100644
> --- a/gnu/services/containers.scm
> +++ b/gnu/services/containers.scm
> @@ -74,6 +74,8 @@ (define-module (gnu services containers)
>              oci-image-system
>              oci-image-grafts?
>  
> +            lower-oci-image-state
> +
>              oci-container-configuration
>              oci-container-configuration?
>              oci-container-configuration-fields
> diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
> index 5e6f39387e7..8cdd86e7ae3 100644
> --- a/gnu/tests/containers.scm
> +++ b/gnu/tests/containers.scm
> @@ -69,13 +69,47 @@ (define %rootless-podman-os
>                            (supplementary-groups '("wheel" "netdev" "cgroup"
>                                                    "audio" "video")))))))
>  
> -(define (run-rootless-podman-test oci-tarball)
> +(define %oci-tarball
> +  (lower-oci-image-state
> +   "guile-guest"
> +   (packages->manifest
> +    (list
> +     guile-3.0 guile-json-3
> +     (package
> +       (name "guest-script")
> +       (version "0")
> +       (source #f)
> +       (build-system trivial-build-system)
> +       (arguments `(#:guile ,guile-3.0
> +                    #:builder
> +                    (let ((out (assoc-ref %outputs "out")))

I'd use a gexp for the builder expression here, with gexp variables,
e.g. #$output.

> +                      (mkdir out)
> +                      (call-with-output-file (string-append out "/a.scm")
> +                        (lambda (port)
> +                          (display "(display \"hello world\n\")" port)))
> +                      #t)))

Trailing #t are no longer part of phases/builders in Guix :-).

> +       (synopsis "Display hello world using Guile")
> +       (description "This package displays the text \"hello world\" on the
> +standard output device and then enters a new line.")
> +       (home-page #f)
> +       (license license:public-domain))))
> +   '(#:entry-point "bin/guile"
> +     #:localstatedir? #t
> +     #:extra-options (#:image-tag "guile-guest")
> +     #:symlinks (("/bin/Guile" -> "bin/guile")
> +                 ("aa.scm" -> "a.scm")))
> +   "guile-guest"
> +   (%current-target-system)
> +   (%current-system)
> +   #f))
> +
> +(define (run-rootless-podman-test)
>  
>    (define os
>      (marionette-operating-system
>       (operating-system-with-gc-roots
>        %rootless-podman-os
> -      (list oci-tarball))
> +      (list %oci-tarball))
>       #:imported-modules '((gnu services herd)
>                            (guix combinators))))
>  
> @@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
>                         (let* ((loaded (slurp ,(string-append #$podman
>                                                               "/bin/podman")
>                                               "load" "-i"
> -                                             ,#$oci-tarball))
> +                                             ,#$%oci-tarball))
>                                (repository&tag "localhost/guile-guest:latest")
>                                (response1 (slurp
>                                            ,(string-append #$podman "/bin/podman")
> @@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
>  
>    (gexp->derivation "rootless-podman-test" test))
>  
> -(define (build-tarball&run-rootless-podman-test)
> -  (mlet* %store-monad
> -      ((_ (set-grafting #f))
> -       (guile (set-guile-for-build (default-guile)))
> -       (guest-script-package ->
> -        (package
> -          (name "guest-script")
> -          (version "0")
> -          (source #f)
> -          (build-system trivial-build-system)
> -          (arguments `(#:guile ,guile-3.0
> -                       #:builder
> -                       (let ((out (assoc-ref %outputs "out")))
> -                         (mkdir out)
> -                         (call-with-output-file (string-append out "/a.scm")
> -                           (lambda (port)
> -                             (display "(display \"hello world\n\")" port)))
> -                         #t)))
> -          (synopsis "Display hello world using Guile")
> -          (description "This package displays the text \"hello world\" on the
> -standard output device and then enters a new line.")
> -          (home-page #f)
> -          (license license:public-domain)))
> -       (profile (profile-derivation (packages->manifest
> -                                     (list guile-3.0 guile-json-3
> -                                           guest-script-package))
> -                                    #:hooks '()
> -                                    #:locales? #f))
> -       (tarball (pack:docker-image
> -                 "docker-pack" profile
> -                 #:symlinks '(("/bin/Guile" -> "bin/guile")
> -                              ("aa.scm" -> "a.scm"))
> -                 #:extra-options
> -                 '(#:image-tag "guile-guest")
> -                 #:entry-point "bin/guile"
> -                 #:localstatedir? #t)))
> -    (run-rootless-podman-test tarball)))
> -
>  (define %test-rootless-podman
>    (system-test
>     (name "rootless-podman")
>     (description "Test rootless Podman service.")
> -   (value (build-tarball&run-rootless-podman-test))))
> +   (value (run-rootless-podman-test))))
>  
>  
>  (define %oci-rootless-podman-os
> diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
> index 5dcf05a17e3..07edd9d5341 100644
> --- a/gnu/tests/docker.scm
> +++ b/gnu/tests/docker.scm
> @@ -26,6 +26,7 @@ (define-module (gnu tests docker)
>    #:use-module (gnu system image)
>    #:use-module (gnu system vm)
>    #:use-module (gnu services)
> +  #:use-module (gnu services containers)
>    #:use-module (gnu services dbus)
>    #:use-module (gnu services networking)
>    #:use-module (gnu services docker)
> @@ -57,6 +58,40 @@ (define %docker-os
>     (service containerd-service-type)
>     (service docker-service-type)))
>  
> +(define %docker-tarball
> +  (lower-oci-image-state
> +   "guile-guest"
> +   (packages->manifest
> +    (list
> +     guile-3.0 guile-json-3
> +     (package
> +       (name "guest-script")
> +       (version "0")
> +       (source #f)
> +       (build-system trivial-build-system)
> +       (arguments `(#:guile ,guile-3.0
> +                    #:builder
> +                    (let ((out (assoc-ref %outputs "out")))
> +                      (mkdir out)
> +                      (call-with-output-file (string-append out "/a.scm")
> +                        (lambda (port)
> +                          (display "(display \"hello world\n\")" port)))
> +                      #t)))

Ditto.

Otherwise LGTM.

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 16 May 2025 01:39:24 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 21:39:24 2025
Received: from localhost ([127.0.0.1]:58731 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFk2g-0004xA-4R
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 21:39:24 -0400
Received: from mail-pf1-x436.google.com ([2607:f8b0:4864:20::436]:54689)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFk2T-0004w2-NH
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 21:39:15 -0400
Received: by mail-pf1-x436.google.com with SMTP id
 d2e1a72fcca58-7424c24f88bso2117688b3a.1
 for <76081 <at> debbugs.gnu.org>; Thu, 15 May 2025 18:39:05 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747359539; x=1747964339; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=1mVWXMXRI9r3LesDU4rd++8dG7/LuM4U9lCS5Qfj6gg=;
 b=Tbg3UA6ZUs7RCPPgM+4jvvPFJX/dEIqRnoVAMTc4uJHOZmVkTGXhGo4UPk87RHV+Pk
 8+FtlMashTDap4YpZzib3VyaATiXU8Bv/9cIrj1D8tB0YLA0iZmkYj5HiI4sZsFvO+II
 mtXnBvWDIQPNAo45Hpzg76AAydstscMfZabNfsHxysfu8A1N5Dq3XGgb0G6Enid9Z5Go
 +4ELWqR7ugwI9FEPua3oN6aI2ADn1/LXh/mwzodiRMyBtbgTPUqxP+qHwo4R0LeIEScO
 /9U3BVTNsxL8vablBeVzgoIEnZI4LmP3ni9s6d3MbWF44ja95jINm+aAQdGNrRlKTjbA
 Aeiw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747359539; x=1747964339;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=1mVWXMXRI9r3LesDU4rd++8dG7/LuM4U9lCS5Qfj6gg=;
 b=p3Q3uSGE+DgUnx/ljdsuD8TUWSEpNwUFHf2k3QLdKL67M1jv9n1FdjEmrXc6KU9BQD
 KCi9HybhIUGedZkPwuNTqFYRh1d2Axabz578y43Mr2axlrvAEWf8+Ud0CBXaxAA67uKI
 39eKMsmtrwblpU+rIo7E//K8QBOqbB3nvpOhJ9TZvfBKERmeh5GHg8p45IChMGgh35sv
 hlAqE0yxeTvryJqxNRZgccYJKOVS2rRSD0UP3uTloKS59PaR0vx6PN6yIp7G8Bi7qyL6
 S52DaRzL2mq8uNV4fzO1GVhFxppFHiQFjFJs40jdaUUq8N4n3P7u/PoNxSLKYrq1uN7T
 qpKQ==
X-Gm-Message-State: AOJu0YwpatwZYsqVmolBLBdUJPqMpPuoIYCunWAxEboe06T0Dd0LH3MD
 WPl7u/IDIND4GBxuBInhTiHABa0Ch1USQYJxtHfFlEIxawL9Ut2iKc+6
X-Gm-Gg: ASbGnctjWlQih8DEXoIjWzFSvMsf7lStFkcLkLixyylPQT7LCwSqmRsmYdFWDSTPrg0
 4gwxzNEZpA4PbpJnHYD381Fxb5gvgkbGH2P5F0j9hsIsuUpls6Q78Pv40v0+oiL4c5/BzJCA7u9
 C9356FvJqnb84V0wUZOEyFZ9ovnf0Y8yzU0DijiDVYieQeqvFdo4Y6THCNqJjJqvFZmcmJz+VLp
 fFcFEEgerToDZKBsFvpQBfkPZZOXS3ROLcE6tIur7LIuAbETznhTo7PXKCTZ9DyzfjNVpsuzdJD
 /rnJpBXX38ShykzeOrV3xA1Wga2Ntsy6WhCN8w2HJSTSiS3NAQ==
X-Google-Smtp-Source: AGHT+IHATmwJo3F0Y31yGl1wyxiu6s07NSis4eX8HCWWaHRjeM3pKKC1fOywLC6+DInOJe64qqC4UA==
X-Received: by 2002:a05:6300:668a:b0:1f5:63f9:9eb4 with SMTP id
 adf61e73a8af0-2170ce33aa9mr1050209637.35.1747359538285; 
 Thu, 15 May 2025 18:38:58 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 41be03b00d2f7-b26eaf8e13csm555489a12.45.2025.05.15.18.38.55
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Thu, 15 May 2025 18:38:57 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 4/7] services: Add oci-service-type.
In-Reply-To: <e3227ad2c6019866db8f7f6e59652ecaec2b7b6f.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:51 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <e3227ad2c6019866db8f7f6e59652ecaec2b7b6f.1746431874.git.goodoldpaul@HIDDEN>
Date: Fri, 16 May 2025 10:38:54 +0900
Message-ID: <87jz6hibbl.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Ludovic =?utf-8?Q?Court=C3=A8s?= <ludo@HIDDEN>,
 Gabriel Wicki <gabriel@HIDDEN>, 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> This patch implements a generalization of the
> oci-container-service-type, which consequently is made deprecated.  The
> oci-service-type, in addition to all the features from the
> oci-container-service-type, can now provision OCI networks and volumes.
> It only handles OCI objects creation, the user is supposed to handle
> state once the objects are provsioned.
>
> It currently supports two different OCI runtimes: Docker and rootless
> Podman.  Both runtimes are tested to make sure provisioned containers
> can connect to each other through provisioned networks and can
> read/write data with provisioned volumes.

Sounds useful!

> At last the Scheme API is thought to facilitate the implementation of a
> Guix Home service in the future.
>
> * gnu/services/containers.scm (%oci-supported-runtimes): New variable;
> (oci-runtime-cli): new variable;
> (oci-runtime-name): new variable;
> (oci-network-configuration): new variable;
> (oci-volume-configuration): new variable;
> (oci-configuration): new variable;
> (oci-extension): new variable;
> (oci-networks-shepherd-name): new variable;
> (oci-service-type): new variable;
> (oci-state->shepherd-services): new variable.

Nitpick: you can combine multiple variables/procedures, like:

(oci-runtime-cli, oci-runtime-name)
(oci-network-configuration, oci-volume-configuration)
[...]
(oci-service-type): New variables.

The above change log appears incomplete, for example oci-image-reference
was changed but isn't listed.

[...]

>  @lisp
> -(service oci-container-service-type
> -         (list
> -          (oci-container-configuration
> -           (network "host")
> -           (image
> -            (oci-image
> -             (repository "guile")
> -             (tag "3")
> -             (value (specifications->manifest '("guile")))
> -             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
> -                             #:max-layers 2))))
> -           (entrypoint "/bin/guile")
> -           (command
> -            '("-c" "(display \"hello!\n\")")))
> -          (oci-container-configuration
> -           (image "prom/prometheus")
> -           (ports
> -             '(("9000" . "9000")
> -               ("9090" . "9090"))))
> -          (oci-container-configuration
> -           (image "grafana/grafana:10.0.1")
> -           (network "host")
> -           (volumes
> -             '("/var/lib/grafana:/var/lib/grafana")))))
> +(simple-service 'oci-provisioning
> +                oci-service-type
> +                (oci-extension
> +                  (networks
> +                    (list
> +                      (oci-network-configuration (name "monitoring"))))
> +                  (containers
> +                   (list
> +                    (oci-container-configuration
> +                     (network "monitoring")
> +                     (image
> +                      (oci-image
> +                        (repository "guile")
> +                        (tag "3")
> +                        (value (specifications->manifest '("guile")))
> +                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
> +                                        #:max-layers 2))))
> +                     (entrypoint "/bin/guile")
> +                     (command
> +                      '("-c" "(display \"hello!\n\")")))
> +                    (oci-container-configuration
> +                      (image "prom/prometheus")
> +                      (network "host")
> +                      (ports
> +                       '(("9000" . "9000")
> +                         ("9090" . "9090"))))
> +                    (oci-container-configuration
> +                      (image "grafana/grafana:10.0.1")
> +                      (network "host")
> +                      (volumes
> +                       '("/var/lib/grafana:/var/lib/grafana")))))))

I was curious: was is the gain to have by using the extension mechanism
instead of directly accepting a list of 'oci-configuration' or similarly
named record to the oci-service-type?

>  @end lisp
>  
>  In this example three different Shepherd services are going to be added to the
>  system.  Each @code{oci-container-configuration} record translates to a
> -@code{docker run} invocation and its fields directly map to options.  You can
> -refer to the
> -@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
> -documentation for the semantics of each value.  If the images are not found,
> -they will be
> -@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
> +@command{docker run} or @command{podman run} invocation and its fields directly

I know not all of these instances are newly added, but @command should
only be used with a single command name.  For a command line, use @samp.

> +map to options.  You can refer to the
> +@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
> +or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
> +upstream documentation for semantics of each value.  If the images are not found,
> +they will be pulled.  You can refer to the
> +@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
> +or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
> +upstream documentation for semantics.  The
>  services with @code{(network "host")} are going to be attached to the
>  host network and are supposed to behave like native processes with regard to
>  networking.
>  
>  @end defvar
>  
> +@c %start of fragment
> +
> +@deftp {Data Type} oci-configuration
> +Available @code{oci-configuration} fields are:
> +
> +@table @asis
> +@item @code{runtime} (default: @code{'docker}) (type: symbol)
> +The OCI runtime to use to run commands.  It can be either @code{'docker} or
> +@code{'podman}.
> +
> +@item @code{runtime-cli} (type: maybe-package-or-string)
> +The OCI runtime command line to be installed in the system profile and used
> +to provision OCI resources, it can be either a package or a string representing
> +an absolute path to the runtime binary entrypoint.  When unset it will default

s/absolute path/absolute file name/

[...]

> +@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
> +The list of @code{oci-container-configuration} records representing the
> +containers to provision.  Most users are supposed not to use this field and use
> +the @code{oci-extension} record instead.

Maybe streamline to 'The use of the @code{oci-extension} record should
be preferred for most cases.'  As I wondered above, what is the
benefit?

> +
> +@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
> +The list of @code{oci-network-configuration} records representing the
> +containers to provision.  Most users are supposed not to use this field and use
> +the @code{oci-extension} record instead.

Ditto.

> +@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
> +The list of @code{oci-volumes-configuration} records representing the
> +containers to provision.  Most users are supposed not to use this field and use
> +the @code{oci-extension} record instead.

Ditto.

[...]

> diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
> index 24f31c756b8..66b46456800 100644
> --- a/gnu/services/containers.scm
> +++ b/gnu/services/containers.scm

[...]

> +(define-configuration/no-serialization oci-volume-configuration
> +  (name
> +   (string)
> +   "The name of the OCI volume to provision.")
> +  (labels
> +   (list '())
> +   "The list of labels that will be used to tag the current volume."
> +   (sanitizer oci-sanitize-labels))
> +  (extra-arguments
> +   (list '())
> +   "A list of strings, gexps or file-like objects that will be directly passed
> +to the @command{docker volume create} or @command{podman volume create}
> +invocation."
> +   (sanitizer oci-sanitize-extra-arguments)))
> +
> +(define (list-of-oci-volumes? value)
> +  (list-of-oci-records? "volumes" oci-volume-configuration? value))
> +
> +(define-configuration/no-serialization oci-network-configuration
> +  (name
> +   (string)
> +   "The name of the OCI network to provision.")
> +  (driver
> +   (maybe-string)
> +   "The driver to manage the network.")
> +  (gateway
> +   (maybe-string)
> +   "IPv4 or IPv6 gateway for the subnet.")
> +  (internal?
> +   (boolean #f)
> +   "Restrict external access to the network")
> +  (ip-range
> +   (maybe-string)
> +   "Allocate container ip from a sub-range in CIDR format.")
> +  (ipam-driver
> +   (maybe-string)
> +   "IP Address Management Driver.")
> +  (ipv6?
> +   (boolean #f)
> +   "Enable IPv6 networking.")
> +  (subnet
> +   (maybe-string)
> +   "Subnet in CIDR format that represents a network segment.")
> +  (labels
> +   (list '())
> +   "The list of labels that will be used to tag the current volume."
> +   (sanitizer oci-sanitize-labels))
> +  (extra-arguments
> +   (list '())
> +   "A list of strings, gexps or file-like objects that will be directly passed
> +to the @command{docker network create} or @command{podman network create}
> +invocation."
> +   (sanitizer oci-sanitize-extra-arguments)))
> +
> +(define (list-of-oci-networks? value)
> +  (list-of-oci-records? "networks" oci-network-configuration? value))
> +
> +(define (package-or-string? value)
> +  (or (package? value) (string? value)))
> +
> +(define-maybe/no-serialization package-or-string)
> +
> +(define-configuration/no-serialization oci-configuration
> +  (runtime
> +   (symbol 'docker)
> +   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
> +@code{'podman}."
> +   (sanitizer oci-sanitize-runtime))

Instead of using explicit sanitizers, it'd be better to define a
oci-runtime? predicate and use it as a type in the configuration.  This
way errors are reported with source location information.

[...]

> +
> +(define (oci-runtime-system-environment runtime user)
> +  (if (eq? runtime 'podman)
> +      (list
> +       #~(string-append
> +          "HOME=" (passwd:dir (getpwnam #$user))))
> +      #~()))
> +
> +(define (oci-runtime-cli runtime runtime-cli path)
> +  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
> +runtime command requested by the user."
> +  (if (string? runtime-cli)
> +      ;; It is a user defined absolute path
> +      runtime-cli
> +      #~(string-append
> +         #$(if (not (maybe-value-set? runtime-cli))
> +               path
> +               runtime-cli)
> +         #$(if (eq? 'podman runtime)
> +               "/bin/podman"
> +               "/bin/docker"))))
> +
> +(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
> +  (let ((runtime-cli
> +         (oci-configuration-runtime-cli config))
> +        (runtime
> +         (oci-configuration-runtime config)))
> +    (oci-runtime-cli runtime runtime-cli path)))
> +
> +(define (oci-runtime-home-cli config)
> +  (let ((runtime-cli
> +         (oci-configuration-runtime-cli config))
> +        (runtime
> +         (oci-configuration-runtime config)))
> +    (oci-runtime-cli runtime runtime-cli
> +                     (string-append (getenv "HOME")
> +                                    "/.guix-home/profile"))))

I'd replace the 'path' instances in the source with 'file' or 'file-name', to
better match our GNU conventions.

[...]

> +(define (oci-object-exists? runtime runtime-cli object verbose?)
> +  #~(lambda* (name #:key (format-string "{{.Name}}"))
> +      (use-modules (ice-9 format)
> +                   (ice-9 match)
> +                   (ice-9 popen)
> +                   (ice-9 rdelim)
> +                   (srfi srfi-1))
> +
> +      (define (read-lines file-or-port)
> +        (define (loop-lines port)
> +          (let loop ((lines '()))
> +            (match (read-line port)
> +              ((? eof-object?)
> +               (reverse lines))
> +              (line
> +               (loop (cons line lines))))))
> +
> +        (if (port? file-or-port)
> +            (loop-lines file-or-port)
> +            (call-with-input-file file-or-port
> +              loop-lines)))
> +
> +      #$(if (eq? runtime 'podman)
> +            #~(let ((command
> +                     (list #$runtime-cli
> +                           #$object "exists" name)))
> +                (when #$verbose?
> +                  (format #t "Running~{ ~a~}~%" command))
> +                (define exit-code (status:exit-val (apply system* command)))
> +                (when #$verbose?
> +                  (format #t "Exit code: ~a~%" exit-code))
> +                (equal? EXIT_SUCCESS exit-code))
> +            #~(let ((command
> +                     (string-append #$runtime-cli
> +                                    " " #$object " ls --format "
> +                                    "\"" format-string "\"")))
> +                (when #$verbose?
> +                  (format #t "Running ~a~%" command))
> +                (member name (read-lines (open-input-pipe command)))))))

That's a complex piece of staged code; have you considered placing it in
a supporting build-side module, e.g. gnu/build/oci-containers.scm ?
Then it could be more simply imported in the gexp environment and used.
See the gnu/build/jami-service.scm for an example.

> +(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
> +  "Return a file-like object that, once lowered, will evaluate to a program able
> +to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
> +  (let ((tarball (lower-oci-image name image)))
>      (with-imported-modules '((guix build utils))
> -      (program-file (format #f "~a-image-loader" name)
> +      (program-file
> +       (format #f "~a-image-loader" name)
>         #~(begin
>             (use-modules (guix build utils)
> +                        (ice-9 match)
>                          (ice-9 popen)
> -                        (ice-9 rdelim))
> -
> -           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
> -           (define line
> -             (read-line
> -              (open-input-pipe
> -               (string-append #$docker " load -i " #$tarball))))
> -
> -           (unless (or (eof-object? line)
> -                       (string-null? line))
> -             (format #t "~a~%" line)
> -             (let ((repository&tag
> -                    (string-drop line
> -                                 (string-length
> -                                   "Loaded image: "))))
> -
> -               (invoke #$docker "tag" repository&tag #$tag)
> -               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
> -
> -(define (oci-container-shepherd-service config)
> -  (define (guess-name name image)
> -    (if (maybe-value-set? name)
> -        name
> -        (string-append "docker-"
> -                       (basename
> -                        (if (string? image)
> -                            (first (string-split image #\:))
> -                            (oci-image-repository image))))))
> -
> -  (let* ((docker (file-append docker-cli "/bin/docker"))
> -         (actions (oci-container-configuration-shepherd-actions config))
> +                        (ice-9 rdelim)
> +                        (srfi srfi-1))
> +           (define object-exists?
> +             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
> +           (define load-command
> +             (string-append #$runtime-cli
> +                            " load -i " #$tarball))
> +
> +           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
> +               (format #t "~a image already exists, skipping.~%" #$tag)
> +               (begin
> +                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
> +                 (when #$verbose?
> +                   (format #t "Running ~a~%" load-command))
> +                 (let ((line (read-line
> +                              (open-input-pipe load-command))))
> +                   (unless (or (eof-object? line)
> +                               (string-null? line))
> +                     (format #t "~a~%" line)
> +                     (let* ((repository&tag
> +                             (string-drop line
> +                                          (string-length
> +                                           "Loaded image: ")))
> +                            (tag-command
> +                             (list #$runtime-cli "tag" repository&tag #$tag))
> +                            (drop-old-tag-command
> +                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
> +
> +                       (unless (string=? repository&tag #$tag)
> +                         (when #$verbose?
> +                           (format #t "Running~{ ~a~}~%" tag-command))
> +
> +                         (let ((exit-code
> +                                (status:exit-val (apply system* tag-command))))
> +                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
> +
> +                           (when #$verbose?
> +                             (format #t "Exit code: ~a~%" exit-code))
> +
> +                           (when (equal? EXIT_SUCCESS exit-code)
> +                             (when #$verbose?
> +                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
> +                             (let ((drop-exit-code
> +                                    (status:exit-val (apply system* drop-old-tag-command))))
> +                               (when #$verbose?
> +                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
> +
> +(define (oci-container-run-invocation runtime runtime-cli name command image-reference
> +                                      options runtime-extra-arguments run-extra-arguments)

That's a lot of arguments!  Perhaps it could accept a record instead?
Please also mind the 80 chars max width.

> +  "Return a list representing the OCI runtime
> +invocation for running containers."
> +  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
> +  `(,runtime-cli ,@runtime-extra-arguments "run" "--rm"
> +    ,@(if (eq? runtime 'podman)
> +          ;; This is because podman takes some time to
> +          ;; release container names.  --replace seems
> +          ;; to be required to be able to restart services.
> +          '("--replace")
> +          '())
> +    "--name" ,name
> +    ,@options ,@run-extra-arguments
> +    ,image-reference ,@command))
> +
> +(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
> +                                   invocation #:key (verbose? #f) (pre-script #~()))

The default value for a keyword argument is #f, so I'd omit the explicit
default for verbose?.

> +  "Return a file-like object that, once lowered, will evaluate to the entrypoint
> +for the Shepherd service that will run IMAGE through RUNTIME-CLI."
> +  (program-file
> +   (string-append "oci-entrypoint-" name)
> +   #~(begin
> +       (use-modules (ice-9 format)
> +                    (srfi srfi-1))
> +       (when #$verbose?
> +         (format #t "Running in verbose mode...~%")
> +         (format #t "Current user: ~a ~a~%"
> +                 (getuid) (passwd:name (getpwuid (getuid))))
> +         (format #t "Current group: ~a ~a~%"
> +                 (getgid) (group:name (getgrgid (getgid))))
> +         (format #t "Current directory ~a~%" (getcwd)))

I'd use a single format call; you can format the string naturally like

            (format #t "\
Running in verbose mode...
Current user: ~a ~a
...
" arg1 arg2, ...)


> +       (define invocation (list #$@invocation))
> +       #$@pre-script
> +       (when #$verbose?
> +         (format #t "Running~{ ~a~}~%" invocation))
> +       (apply execlp `(,(first invocation) ,@invocation)))))

You can more simply: (apply execlp (first invocation) invocation)
> +
> +(define* (oci-container-shepherd-service runtime runtime-cli config
> +                                         #:key
> +                                         (runtime-environment #~())
> +                                         (runtime-extra-arguments '())
> +                                         (oci-requirement '())
> +                                         (user #f)
> +                                         (group #f)
> +                                         (verbose? #f))
> +  "Return a Shepherd service object that will run the OCI container represented
> +by CONFIG through RUNTIME-CLI."
> +  (let* ((actions (oci-container-configuration-shepherd-actions config))
>           (auto-start?
>            (oci-container-configuration-auto-start? config))
> -         (user (oci-container-configuration-user config))
> -         (group (oci-container-configuration-group config))
> +         (maybe-user (oci-container-configuration-user config))
> +         (maybe-group (oci-container-configuration-group config))
> +         (user (if (maybe-value-set? maybe-user)
> +                    maybe-user
> +                    user))
> +         (group (if (maybe-value-set? maybe-group)
> +                    maybe-group
> +                    group))
>           (host-environment
>            (oci-container-configuration-host-environment config))
>           (command (oci-container-configuration-command config))
>           (log-file (oci-container-configuration-log-file config))
> -         (provision (oci-container-configuration-provision config))
>           (requirement (oci-container-configuration-requirement config))
>           (respawn?
>            (oci-container-configuration-respawn? config))
>           (image (oci-container-configuration-image config))
>           (image-reference (oci-image-reference image))
>           (options (oci-container-configuration->options config))
> -         (name (guess-name provision image))
> +         (name
> +          (oci-container-shepherd-name runtime config))
>           (extra-arguments
> -          (oci-container-configuration-extra-arguments config)))
> +          (oci-container-configuration-extra-arguments config))
> +         (invocation
> +          (oci-container-run-invocation
> +           runtime runtime-cli name command image-reference
> +           options runtime-extra-arguments extra-arguments))
> +         (container-action
> +          (lambda* (command #:key (environment-variables #f))
> +            #~(lambda _
> +                (fork+exec-command
> +                 (list #$@command)
> +                 #$@(if user (list #:user user) '())
> +                 #$@(if group (list #:group group) '())
> +                 #$@(if (maybe-value-set? log-file)
> +                        (list #:log-file log-file)
> +                        '())
> +                 #$@(if (and user (eq? runtime 'podman))
> +                        (list #:directory
> +                              #~(passwd:dir (getpwnam #$user)))
> +                        '())
> +                 #$@(if environment-variables
> +                        (list #:environment-variables
> +                              environment-variables)
> +                        '()))))))

That's a very long let.  Since many values appear to simply be accessed
from theh oci-container-configuration, match-record could be used to
great effect.  For readability, I'd also define the action procedures
using nested 'define' inside the let/match-record.

[...]


[...]

> +              (begin
> +                (when #$verbose?
> +                  (format #t "Running~{ ~a~}~%" invocation))
> +                (let ((exit-code (status:exit-val (apply system* invocation))))
> +                  (when #$verbose?
> +                    (format #t "Exit code: ~a~%" exit-code))))))
> +        (list #$@invocations)))))
> +
> +(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invocations

Max width :-).

> +                                      #:key
> +                                      (runtime-environment #~())
> +                                      (user #f)
> +                                      (group #f)
> +                                      (verbose? #f))

I'd drop the default #f values, here and elsewhere.

[...]

> +(define (oci-service-subids config)
> +  "Return a subids-extension record representing subuids and subgids required by
> +the rootless Podman backend."
> +  (define (delete-duplicate-ranges ranges)
> +    (delete-duplicates ranges
> +                       (lambda args
> +                         (apply string=? (map subid-range-name ranges)))))

I don't understand how the above 'delete-duplicate-ranges' procedure
works; the 2nd argument of delete-duplicates is normally a predicate
that accepts two values, but here it's a lambda that disregards the
arguments: it seems it'd be wasteful to compute the same result over and
over?

> +  (define runtime
> +    (oci-configuration-runtime config))
> +  (define user
> +    (oci-configuration-user config))
> +  (define subgids (oci-configuration-subgids-range config))
> +  (define subuids (oci-configuration-subuids-range config))
> +  (define container-users
> +    (filter (lambda (range)
> +              (and (maybe-value-set?
> +                    (subid-range-name range))
> +                   (not (string=? (subid-range-name range) user))))
> +            (map (lambda (container)
> +                   (subid-range
> +                    (name
> +                     (oci-container-configuration-user container))))
> +                 (oci-configuration-containers config))))
> +  (define subgid-ranges
> +    (delete-duplicate-ranges
> +     (cons
> +      (if (not (maybe-value-set? subgids))
> +          (subid-range (name user))
> +          subgids)
> +      container-users)))
> +  (define subuid-ranges
> +    (delete-duplicate-ranges
> +     (cons
> +      (if (not (maybe-value-set? subuids))

Here and above, I'd reverse your if branches, so you do not need to use 'not'.

> +          (subid-range (name user))
> +          subuids)
> +      container-users)))
> +
> +  (if (eq? 'podman runtime)
> +      (subids-extension
> +       (subgids
> +        subgid-ranges)
> +       (subuids
> +        subuid-ranges))
> +      (subids-extension)))
> +
> +(define (oci-objects-merge-lst a b object get-name)
> +  (define (contains? value lst)
> +    (member value (map get-name lst)))
> +  (let loop ((merged '())
> +             (lst (append a b)))
> +    (if (null? lst)
> +        merged
> +        (loop
> +         (let ((element (car lst)))
> +           (when (contains? element merged)
> +             (raise
> +              (formatted-message
> +               (G_ "Duplicated ~a: ~a. ~as names should be unique, please

s/~as names/names of ~a/, perhaps?

> +remove the duplicate.") object (get-name element) object)))
> +           (cons element merged))
> +         (cdr lst)))))
> +
> +(define (oci-extension-merge a b)
> +  (oci-extension
> +   (containers (oci-objects-merge-lst
> +                (oci-extension-containers a)
> +                (oci-extension-containers b)
> +                "container"
> +                (lambda (config)
> +                  (define maybe-name (oci-container-configuration-provision config))

Max width.

[...]

> +(define oci-service-type
> +  (service-type (name 'oci)
> +                (extensions
> +                 (list
> +                  (service-extension profile-service-type
> +                                     (lambda (config)
> +                                       (let ((runtime-cli
> +                                              (oci-configuration-runtime-cli config))
> +                                             (runtime
> +                                              (oci-configuration-runtime config)))
> +                                         (oci-service-profile runtime runtime-cli))))

You can nest the fields under (service-type to meet the max columns width.

> +                  (service-extension subids-service-type
> +                                     oci-service-subids)
> +                  (service-extension account-service-type
> +                                     oci-service-accounts)
> +                  (service-extension shepherd-root-service-type
> +                                     oci-configuration->shepherd-services)))
> +                ;; Concatenate OCI object lists.
> +                (compose (lambda (args)
> +                           (fold oci-extension-merge
> +                                 (oci-extension)
> +                                 args)))
> +                (extend oci-configuration-extend)
> +                (default-value (oci-configuration))
> +                (description
> +                 "This service implements the provisioning of OCI object such

s/OCI object/OCI objects/

> +as containers, networks and volumes.")))
> diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
> index 828ceea313a..125e748bb0e 100644
> --- a/gnu/services/docker.scm
> +++ b/gnu/services/docker.scm
> @@ -31,7 +31,10 @@ (define-module (gnu services docker)
>    #:use-module (gnu system shadow)
>    #:use-module (gnu packages docker)
>    #:use-module (gnu packages linux)               ;singularity
> +  #:use-module (guix deprecation)
> +  #:use-module (guix diagnostics)
>    #:use-module (guix gexp)
> +  #:use-module (guix i18n)
>    #:use-module (guix records)
>    #:use-module (srfi srfi-1)
>    #:use-module (ice-9 format)
> @@ -67,16 +70,18 @@ (define-module (gnu services docker)
>                 oci-container-configuration-volumes
>                 oci-container-configuration-container-user
>                 oci-container-configuration-workdir
> -               oci-container-configuration-extra-arguments
> -               oci-container-shepherd-service
> -               %oci-container-accounts)
> +               oci-container-configuration-extra-arguments)
>  
>    #:export (containerd-configuration
>              containerd-service-type
>              docker-configuration
>              docker-service-type
>              singularity-service-type
> -            oci-container-service-type))
> +            ;; for backwards compatibility, until the
> +            ;; oci-container-service-type is fully deprecated

Please punctuate fully standalone comments (here, capitalizing the first
letter and adding a trailing period).

> +            oci-container-shepherd-service
> +            oci-container-service-type
> +            %oci-container-accounts))
>  
>  (define-maybe file-like)
>  
> @@ -297,17 +302,25 @@ (define singularity-service-type
>  ;;; OCI container.
>  ;;;
>  
> -(define (configs->shepherd-services configs)
> -  (map oci-container-shepherd-service configs))
> +;; for backwards compatibility, until the
> +;; oci-container-service-type is fully deprecated

Ditto.

> +(define-deprecated (oci-container-shepherd-service config)
> +  oci-service-type
> +  ((@ (gnu services containers) oci-container-shepherd-service)
> +   'docker config))
> +(define %oci-container-accounts
> +  (filter user-account? (oci-service-accounts (oci-configuration))))
>  
>  (define oci-container-service-type
>    (service-type (name 'oci-container)
> -                (extensions (list (service-extension profile-service-type
> -                                                     (lambda _ (list docker-cli)))
> -                                  (service-extension account-service-type
> -                                                     (const %oci-container-accounts))
> -                                  (service-extension shepherd-root-service-type
> -                                                     configs->shepherd-services)))
> +                (extensions
> +                 (list (service-extension oci-service-type
> +                                          (lambda (containers)
> +                                            (warning
> +                                             (G_
> +                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))

Max width; you can break long strings with \ (backslash) escape.

> +                                            (oci-extension
> +                                             (containers containers))))))
>                  (default-value '())
>                  (extend append)
>                  (compose concatenate)
> diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
> index 0ecc8ddb126..5e6f39387e7 100644
> --- a/gnu/tests/containers.scm
> +++ b/gnu/tests/containers.scm

[...]

> +(define (run-rootless-podman-oci-service-test)
> +  (define os
> +    (marionette-operating-system
> +     (operating-system-with-gc-roots
> +      %oci-rootless-podman-os
> +      (list))
> +     #:imported-modules '((gnu services herd)
> +                          (guix combinators))))
> +
> +  (define vm
> +    (virtual-machine
> +     (operating-system os)
> +     (volatile? #f)
> +     (memory-size 1024)
> +     (disk-image-size (* 3000 (expt 2 20)))
> +     (port-forwardings '())))
> +
> +  (define test
> +    (with-imported-modules '((gnu build marionette))
> +      #~(begin
> +          (use-modules (srfi srfi-11) (srfi srfi-64)
> +                       (gnu build marionette))
> +
> +          (define marionette
> +            ;; Relax timeout to accommodate older systems and
> +            ;; allow for pulling the image.
> +            (make-marionette (list #$vm) #:timeout 60))
> +          (define out-dir "/tmp")
> +
> +          (test-runner-current (system-test-runner #$output))
> +          (test-begin "rootless-podman-oci-service")
> +
> +          (marionette-eval
> +           '(begin
> +              (use-modules (gnu services herd))
> +              (wait-for-service 'user-processes))
> +           marionette)
> +
> +          (test-assert "rootless-podman services started successfully"
> +           (begin
> +             (define (run-test)
> +               (marionette-eval
> +                `(begin
> +                   (use-modules (ice-9 popen)
> +                                (ice-9 match)
> +                                (ice-9 rdelim))
> +
> +                   (define (read-lines file-or-port)
> +                     (define (loop-lines port)
> +                       (let loop ((lines '()))
> +                         (match (read-line port)
> +                           ((? eof-object?)
> +                            (reverse lines))
> +                           (line
> +                            (loop (cons line lines))))))
> +
> +                     (if (port? file-or-port)
> +                         (loop-lines file-or-port)
> +                         (call-with-input-file file-or-port
> +                           loop-lines)))
> +
> +                   (define slurp
> +                     (lambda args
> +                       (let* ((port (apply open-pipe* OPEN_READ args))
> +                              (output (read-lines port))
> +                              (status (close-pipe port)))
> +                         output)))
> +                   (let* ((bash
> +                           ,(string-append #$bash "/bin/bash"))

Unquote-splice is not necessary here, you can more directly #$(string-append
bash "/bin/bash").

> +                          (response1
> +                           (slurp bash "-c"
> +                                  (string-append "ls -la /sys/fs/cgroup | "
> +                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
> +                          (response2 (slurp bash "-c"
> +                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
> +                                                           ".{procs,subtree_control,threads} | "
> +                                                           "awk '{ print $4 }' | sort -u"))))

Is there perhaps an easier way to check that the podman services were
started successfully?  Perhaps using the podman command directly to
query the state of the running containers?

'ls' is usually frowned for scripting; it's meant for interactive use.
Perhaps you could use the shell wildcard feature or the find command
instead?  I comment explaining what the above does/check exactly would
be useful; it's not obvious.

> +                     (list (string-join response1 "\n") (string-join response2 "\n"))))
> +                marionette))
> +             ;; Allow services to come up on slower machines
                                                               ^ missing period
                                                              
> +             (let loop ((attempts 0))
> +               (if (= attempts 60)
> +                   (error "Services didn't come up after more than 60 seconds")
> +                   (if (equal? '("cgroup" "cgroup")
> +                               (run-test))
> +                       #t
> +                       (begin
> +                         (sleep 1)
> +                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
> +                                 (+ 1 attempts))
> +                         (loop (+ 1 attempts))))))))

Note that we have a 'with-retries' definition in (gnu build
jami-service) which could be used here;  perhaps it should be relocated
elsewhere.

> +
> +          (test-assert "podman-volumes running"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (srfi srfi-1)
> +                                 (ice-9 popen)
> +                                 (ice-9 match)
> +                                 (ice-9 rdelim))
> +
> +                    (define (wait-for-file file)
> +                      ;; Wait until FILE shows up.
> +                      (let loop ((i 6))
> +                        (cond ((file-exists? file)
> +                               #t)
> +                              ((zero? i)
> +                               (error "file didn't show up" file))
> +                              (else
> +                               (pk 'wait-for-file file)
> +                               (sleep 1)
> +                               (loop (- i 1))))))
> +
> +                    (define (read-lines file-or-port)
> +                      (define (loop-lines port)
> +                        (let loop ((lines '()))
> +                          (match (read-line port)
> +                            ((? eof-object?)
> +                             (reverse lines))
> +                            (line
> +                             (loop (cons line lines))))))
> +
> +                      (if (port? file-or-port)
> +                          (loop-lines file-or-port)
> +                          (call-with-input-file file-or-port
> +                            loop-lines)))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ
> +                                            (list "sh" "-l" "-c"
> +                                                  (string-join
> +                                                   args
> +                                                   " "))))
> +                               (output (read-lines port))
> +                               (status (close-pipe port)))
> +                          output)))
> +
> +                    (match (primitive-fork)
> +                      (0
> +                       (dynamic-wind
> +                         (const #f)
> +                         (lambda ()
> +                           (setgid (passwd:gid (getpwnam "oci-container")))
> +                           (setuid (passwd:uid (getpw "oci-container")))
> +
> +                           (let ((response (slurp
> +                                            "/run/current-system/profile/bin/podman"
> +                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
> +                                            "|" "tr" "' '" "'\n'")))
> +
> +                             (call-with-output-file (string-append ,out-dir "/response")

I'd remove 'out-dir' and hard-code "/tmp/response" directly.


> +                               (lambda (port)
> +                                 (display (string-join response "\n") port)))))
> +                         (lambda ()
> +                           (primitive-exit 127))))

The fork is used to be able to run the podman command with the right
passwd and uid, right?  Maybe you could use the shepherd's provided
'command' or 'fork+exec-command', which have a nice API to do this.
Also, is the dynamic-wind unnecessary?  I would have thought that if a
forked children aborts, its exit status should be reflected accordingly?

> +                      (pid
> +                       (cdr (waitpid pid))))
> +
> +                    (wait-for-file (string-append ,out-dir "/response"))
> +
> +                    (stable-sort
> +                     (slurp "cat" (string-append ,out-dir "/response"))
> +                     string<=?))
> +                 marionette))
> +              ;; Allow services to come up on slower machines

missing trailing '.'.

> +              (let loop ((attempts 0))
> +                (if (= attempts 80)
> +                    (error "Service didn't come up after more than 80 seconds")

nitpick: error messages do not need to be complete sentences/be punctuated, as
they are intended for debugging.

> +                    (if (equal? '("my-volume")
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))
> +
> +          (test-assert "podman-networks running"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (srfi srfi-1)
> +                                 (ice-9 popen)
> +                                 (ice-9 match)
> +                                 (ice-9 rdelim))
> +
> +                    (define (wait-for-file file)
> +                      ;; Wait until FILE shows up.
> +                      (let loop ((i 6))
> +                        (cond ((file-exists? file)
> +                               #t)
> +                              ((zero? i)
> +                               (error "file didn't show up" file))
> +                              (else
> +                               (pk 'wait-for-file file)
> +                               (sleep 1)
> +                               (loop (- i 1))))))
> +
> +                    (define (read-lines file-or-port)
> +                      (define (loop-lines port)
> +                        (let loop ((lines '()))
> +                          (match (read-line port)
> +                            ((? eof-object?)
> +                             (reverse lines))
> +                            (line
> +                             (loop (cons line lines))))))
> +
> +                      (if (port? file-or-port)
> +                          (loop-lines file-or-port)
> +                          (call-with-input-file file-or-port
> +                            loop-lines)))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ
> +                                            (list "sh" "-l" "-c"
> +                                                  (string-join
> +                                                   args
> +                                                   " "))))
> +                               (output (read-lines port))
> +                               (status (close-pipe port)))
> +                          output))

The definitions boilerplate could be defined as data above the tests and
spliced in for reuse.

> +                    (match (primitive-fork)
> +                      (0
> +                       (dynamic-wind
> +                         (const #f)
> +                         (lambda ()
> +                           (setgid (passwd:gid (getpwnam "oci-container")))
> +                           (setuid (passwd:uid (getpw "oci-container")))
> +
> +                           (let ((response (slurp
> +                                            "/run/current-system/profile/bin/podman"
> +                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
> +                                            "|" "tr" "' '" "'\n'")))

Max width :-).

> +                             (call-with-output-file (string-append ,out-dir "/response")
> +                               (lambda (port)
> +                                 (display (string-join response "\n") port)))))
> +                         (lambda ()
> +                           (primitive-exit 127))))
> +                      (pid
> +                       (cdr (waitpid pid))))
> +
> +                    (wait-for-file (string-append ,out-dir "/response"))
> +
> +                    (stable-sort
> +                     (slurp "cat" (string-append ,out-dir "/response"))
> +                     string<=?))

I don't think using the stable variant here is necessary/useful.

> +                 marionette))
> +              ;; Allow services to come up on slower machines
> +              (let loop ((attempts 0))
> +                (if (= attempts 80)
> +                    (error "Service didn't come up after more than 80 seconds")
> +                    (if (equal? '("my-network" "podman")
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))
> +
> +          (test-assert "first container running"
> +            (marionette-eval
> +             '(begin
> +                (use-modules (gnu services herd))
> +                (wait-for-service 'first #:timeout 120)
> +                #t)

No need for #t.

> +             marionette))
> +
> +          (test-assert "second container running"
> +            (marionette-eval
> +              '(begin
> +                (use-modules (gnu services herd))
> +                (wait-for-service 'second #:timeout 120)
> +                #t)

Ditto.

> +             marionette))
> +
> +          (test-assert "passing host environment variables"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (srfi srfi-1)
> +                                 (ice-9 popen)
> +                                 (ice-9 match)
> +                                 (ice-9 rdelim))
> +
> +                    (define (wait-for-file file)
> +                      ;; Wait until FILE shows up.
> +                      (let loop ((i 60))
> +                        (cond ((file-exists? file)
> +                               #t)
> +                              ((zero? i)
> +                               (error "file didn't show up" file))
> +                              (else
> +                               (pk 'wait-for-file file)
> +                               (sleep 1)
> +                               (loop (- i 1))))))
> +
> +                    (define (read-lines file-or-port)
> +                      (define (loop-lines port)
> +                        (let loop ((lines '()))
> +                          (match (read-line port)
> +                            ((? eof-object?)
> +                             (reverse lines))
> +                            (line
> +                             (loop (cons line lines))))))
> +
> +                      (if (port? file-or-port)
> +                          (loop-lines file-or-port)
> +                          (call-with-input-file file-or-port
> +                            loop-lines)))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ
> +                                            (list "sh" "-l" "-c"
> +                                                  (string-join
> +                                                   args
> +                                                   " "))))
> +                               (output (read-lines port))
> +                               (status (close-pipe port)))
> +                          output)))
> +
> +                    (match (primitive-fork)
> +                      (0
> +                       (dynamic-wind
> +                         (const #f)
> +                         (lambda ()
> +                           (setgid (passwd:gid (getpwnam "oci-container")))
> +                           (setuid (passwd:uid (getpw "oci-container")))
> +
> +                           (let ((response (slurp
> +                                            "/run/current-system/profile/bin/podman"
> +                                            "exec" "first"
> +                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
> +                             (call-with-output-file (string-append ,out-dir "/response")
> +                               (lambda (port)
> +                                 (display (string-join response "\n") port)))))
> +                         (lambda ()
> +                           (primitive-exit 127))))
> +                      (pid
> +                       (cdr (waitpid pid))))
> +
> +                    (wait-for-file (string-append ,out-dir "/response"))
> +                    (slurp "cat" (string-append ,out-dir "/response")))
> +                 marionette))
> +              ;; Allow image to be loaded on slower machines
> +              (let loop ((attempts 0))
> +                (if (= attempts 180)
> +                    (error "Service didn't come up after more than 180 seconds")
> +                    (if (equal? (list "value")
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))

I'm curious, did you test this on a slow machine to see how much time it
needed there?

> +          (test-equal "mounting host files"

[...]

> +                (match (primitive-fork)
> +                  (0
> +                   (dynamic-wind
> +                     (const #f)
> +                     (lambda ()
> +                       (setgid (passwd:gid (getpwnam "oci-container")))
> +                       (setuid (passwd:uid (getpw "oci-container")))
> +
> +                       (let ((response (slurp
> +                                        "/run/current-system/profile/bin/podman"
> +                                        "exec" "second"
> +                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(display (call-with-input-file \"/shared.txt\" read-line)))'")))
> +                         (call-with-output-file (string-append ,out-dir "/response")
> +                           (lambda (port)
> +                             (display (string-join response " ") port)))))
> +                     (lambda ()
> +                       (primitive-exit 127))))
> +                  (pid
> +                   (cdr (waitpid pid))))
> +
> +                (wait-for-file (string-append ,out-dir "/response"))
> +                (slurp "cat" (string-append ,out-dir "/response")))
> +             marionette))
> +
> +          (test-equal "write to volumes"
> +            '("world")
> +            (marionette-eval

[...]

> +                (match (primitive-fork)
> +                  (0
> +                   (dynamic-wind
> +                     (const #f)
> +                     (lambda ()
> +                       (setgid (passwd:gid (getpwnam "oci-container")))
> +                       (setuid (passwd:uid (getpw "oci-container")))
> +
> +                       (slurp
> +                        "/run/current-system/profile/bin/podman"
> +                        "exec" "first"
> +                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
> +
> +                       (let ((response (slurp
> +                                        "/run/current-system/profile/bin/podman"
> +                                        "exec" "second"
> +                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
> +                         (call-with-output-file (string-append ,out-dir "/response")
> +                           (lambda (port)
> +                             (display (string-join response " ") port)))))
> +                     (lambda ()
> +                       (primitive-exit 127))))
> +                  (pid
> +                   (cdr (waitpid pid))))

The idiom of forking ot invoke podman is used so commonly it should be
extracted to a procedure or syntax and reused.

> +
> +                (wait-for-file (string-append ,out-dir "/response"))
> +                (slurp "cat" (string-append ,out-dir "/response")))
> +             marionette))
> +
> +          (test-equal "can read ports over network"
> +            '("out of office")
> +            (marionette-eval
> +             `(begin

[...]

> +                (match (primitive-fork)
> +                  (0
> +                   (dynamic-wind
> +                     (const #f)
> +                     (lambda ()
> +                       (setgid (passwd:gid (getpwnam "oci-container")))
> +                       (setuid (passwd:uid (getpw "oci-container")))
> +
> +                       (let ((response (slurp
> +                                        "/run/current-system/profile/bin/podman"
> +                                        "exec" "second"
> +                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
> +(define-values (response out)
> +  (http-get \"http://first:8080\"))
> +(display out))'")))
> +                         (call-with-output-file (string-append ,out-dir "/response")
> +                           (lambda (port)
> +                             (display (string-join response " ") port)))))
> +                     (lambda ()
> +                       (primitive-exit 127))))
> +                  (pid
> +                   (cdr (waitpid pid))))
> +
> +                (wait-for-file (string-append ,out-dir "/response"))
> +                (slurp "cat" (string-append ,out-dir "/response")))
> +             marionette))
> +
> +          (test-end))))
> +
> +  (gexp->derivation "rootless-podman-oci-service-test" test))
> +
> +(define %test-oci-service-rootless-podman
> +  (system-test
> +   (name "oci-service-rootless-podman")
> +   (description "Test Rootless-Podman backed OCI provisioning service.")
> +   (value (run-rootless-podman-oci-service-test))))
> +
> +(define %oci-docker-os
> +  (simple-operating-system
> +   (service dhcp-client-service-type)
> +   (service dbus-root-service-type)
> +   (service polkit-service-type)
> +   (service elogind-service-type)
> +   (service containerd-service-type)
> +   (service docker-service-type)
> +   (extra-special-file "/shared.txt"
> +                       (plain-file "shared.txt" "hello"))
> +   (service oci-service-type
> +            (oci-configuration
> +             (verbose? #t)))
> +   (simple-service 'oci-provisioning
> +                   oci-service-type
> +                   (oci-extension
> +                    (networks
> +                     (list (oci-network-configuration (name "my-network"))))
> +                    (volumes
> +                     (list (oci-volume-configuration (name "my-volume"))))
> +                    (containers
> +                     (list
> +                      (oci-container-configuration
> +                       (provision "first")
> +                       (image
> +                        (oci-image
> +                         (repository "guile")
> +                         (value
> +                          (specifications->manifest '("guile")))
> +                         (pack-options
> +                          '(#:symlinks (("/bin" -> "bin"))))))
> +                       (entrypoint "/bin/guile")
> +                       (network "my-network")
> +                       (command
> +                        '("-c" "(use-modules (web server))
> +(define (handler request request-body)
> +  (values '((content-type . (text/plain))) \"out of office\"))
> +(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
> +                       (host-environment
> +                        '(("VARIABLE" . "value")))
> +                       (volumes
> +                        '(("my-volume" . "/my-volume")))
> +                       (extra-arguments
> +                        '("--env" "VARIABLE")))
> +                      (oci-container-configuration
> +                       (provision "second")
> +                       (image
> +                        (oci-image
> +                         (repository "guile")
> +                         (value
> +                          (specifications->manifest '("guile")))
> +                         (pack-options
> +                          '(#:symlinks (("/bin" -> "bin"))))))
> +                       (entrypoint "/bin/guile")
> +                       (network "my-network")
> +                       (command
> +                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))

You can indent the snippet normally on multiple lines, for readability,
or extract it as some expression to splice in.

> +                       (volumes
> +                        '(("my-volume" . "/my-volume")
> +                          ("/shared.txt" . "/shared.txt:ro"))))))))))
> +
> +(define (run-docker-oci-service-test)
> +  (define os
> +    (marionette-operating-system
> +     (operating-system-with-gc-roots
> +      %oci-docker-os
> +      (list))
> +     #:imported-modules '((gnu services herd)
> +                          (guix combinators))))
> +
> +  (define vm
> +    (virtual-machine
> +     (operating-system os)
> +     (volatile? #f)
> +     (memory-size 1024)
> +     (disk-image-size (* 3000 (expt 2 20)))
> +     (port-forwardings '())))
> +
> +  (define test
> +    (with-imported-modules '((gnu build marionette))
> +      #~(begin
> +          (use-modules (srfi srfi-11) (srfi srfi-64)
> +                       (gnu build marionette))
> +
> +          (define marionette
> +            ;; Relax timeout to accommodate older systems and
> +            ;; allow for pulling the image.
> +            (make-marionette (list #$vm) #:timeout 60))
> +
> +          (test-runner-current (system-test-runner #$output))
> +          (test-begin "docker-oci-service")
> +
> +          (marionette-eval
> +           '(begin
> +              (use-modules (gnu services herd))
> +              (wait-for-service 'dockerd))
> +           marionette)
> +
> +          (test-assert "docker-volumes running"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (ice-9 popen)
> +                                 (ice-9 rdelim))
> +
> +                    (define (read-lines file-or-port)
> +                      (define (loop-lines port)
> +                        (let loop ((lines '()))
> +                          (match (read-line port)
> +                            ((? eof-object?)
> +                             (reverse lines))
> +                            (line
> +                             (loop (cons line lines))))))
> +
> +                      (if (port? file-or-port)
> +                          (loop-lines file-or-port)
> +                          (call-with-input-file file-or-port
> +                            loop-lines)))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ
> +                                            (list "sh" "-l" "-c"
> +                                                  (string-join
> +                                                   args
> +                                                   " "))))
> +                               (output (read-lines port))
> +                               (status (close-pipe port)))
> +                          output)))
> +
> +                    (stable-sort
> +                     (slurp
> +                      "/run/current-system/profile/bin/docker"
> +                      "volume" "ls" "--format" "\"{{.Name}}\"")
> +                     string<=?))
> +
> +                 marionette))
> +              ;; Allow services to come up on slower machines
> +              (let loop ((attempts 0))
> +                (if (= attempts 80)
> +                    (error "Service didn't come up after more than 80 seconds")
> +                    (if (equal? '("my-volume")
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))
> +
> +          (test-assert "docker-networks running"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (ice-9 popen)
> +                                 (ice-9 rdelim))
> +
> +                    (define (read-lines file-or-port)
> +                      (define (loop-lines port)
> +                        (let loop ((lines '()))
> +                          (match (read-line port)
> +                            ((? eof-object?)
> +                             (reverse lines))
> +                            (line
> +                             (loop (cons line lines))))))
> +
> +                      (if (port? file-or-port)
> +                          (loop-lines file-or-port)
> +                          (call-with-input-file file-or-port
> +                            loop-lines)))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ
> +                                            (list "sh" "-l" "-c"
> +                                                  (string-join
> +                                                   args
> +                                                   " "))))
> +                               (output (read-lines port))
> +                               (status (close-pipe port)))
> +                          output)))
> +
> +                    (stable-sort
> +                     (slurp
> +                      "/run/current-system/profile/bin/docker"
> +                      "network" "ls" "--format" "\"{{.Name}}\"")
> +                     string<=?))
> +
> +                 marionette))
> +              ;; Allow services to come up on slower machines
> +              (let loop ((attempts 0))
> +                (if (= attempts 80)
> +                    (error "Service didn't come up after more than 80 seconds")
> +                    (if (equal? '("bridge" "host" "my-network" "none")
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))
> +
> +          (test-assert "first container running"
> +            (marionette-eval
> +             '(begin
> +                (use-modules (gnu services herd))
> +                (wait-for-service 'first #:timeout 120)
> +                #t)
> +             marionette))
> +
> +          (test-assert "second container running"
> +           (marionette-eval
> +            '(begin
> +               (use-modules (gnu services herd))
> +               (wait-for-service 'second #:timeout 120)
> +               #t)
> +            marionette))
> +
> +          (test-assert "passing host environment variables"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (ice-9 popen)
> +                                 (ice-9 rdelim))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ args))
> +                               (output (let ((line (read-line port)))
> +                                         (if (eof-object? line)
> +                                             ""
> +                                             line)))
> +                               (status (close-pipe port)))
> +                          output)))
> +
> +                    (slurp
> +                     "/run/current-system/profile/bin/docker"
> +                     "exec" "first"
> +                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
> +                 marionette))
> +              ;; Allow image to be loaded on slower machines
> +              (let loop ((attempts 0))
> +                (if (= attempts 180)
> +                    (error "Service didn't come up after more than 180 seconds")
> +                    (if (equal? "value"
> +                                (run-test))
> +                        #t
> +                        (begin
> +                          (sleep 1)
> +                          (loop (+ 1 attempts))))))))
> +
> +          (test-equal "mounting host files"
> +            "hello"
> +            (marionette-eval
> +             `(begin
> +                (use-modules (ice-9 popen)
> +                             (ice-9 rdelim))
> +
> +                (define slurp
> +                  (lambda args
> +                    (let* ((port (apply open-pipe* OPEN_READ args))
> +                           (output (let ((line (read-line port)))
> +                                     (if (eof-object? line)
> +                                         ""
> +                                         line)))
> +                           (status (close-pipe port)))
> +                      output)))
> +
> +                (slurp
> +                 "/run/current-system/profile/bin/docker"
> +                 "exec" "second"
> +                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(display (call-with-input-file \"/shared.txt\" read-line)))"))
> +             marionette))
> +
> +          (test-equal "write to volumes"
> +            "world"
> +            (marionette-eval
> +             `(begin
> +                (use-modules (ice-9 popen)
> +                             (ice-9 rdelim))
> +
> +                (define slurp
> +                  (lambda args
> +                    (let* ((port (apply open-pipe* OPEN_READ args))
> +                           (output (let ((line (read-line port)))
> +                                     (if (eof-object? line)
> +                                         ""
> +                                         line)))
> +                           (status (close-pipe port)))
> +                      output)))
> +
> +                (slurp
> +                 "/run/current-system/profile/bin/docker"
> +                 "exec" "first"
> +                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
> +                (slurp
> +                 "/run/current-system/profile/bin/docker"
> +                 "exec" "second"
> +                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
> +             marionette))
> +
> +          (test-equal "can read ports over network"
> +            "out of office"
> +            (marionette-eval
> +             `(begin
> +                (use-modules (ice-9 popen)
> +                             (ice-9 rdelim))
> +
> +                (define slurp
> +                  (lambda args
> +                    (let* ((port (apply open-pipe* OPEN_READ args))
> +                           (output (let ((line (read-line port)))
> +                                     (if (eof-object? line)
> +                                         ""
> +                                         line)))
> +                           (status (close-pipe port)))
> +                      output)))
> +
> +                (slurp
> +                 "/run/current-system/profile/bin/docker"
> +                 "exec" "second"
> +                 "/bin/guile" "-c" "(begin (use-modules (web client))
> +(define-values (response out)
> +  (http-get \"http://first:8080\"))
> +(display out))"))
> +             marionette))
> +
> +          (test-end))))
> +
> +  (gexp->derivation "docker-oci-service-test" test))
> +
> +(define %test-oci-service-docker
> +  (system-test
> +   (name "oci-service-docker")
> +   (description "Test Docker backed OCI provisioning service.")
> +   (value (run-docker-oci-service-test))))

Make sure to mirror my earlier comments to the docker test as well.

Phew, that was a large chunk!  I'll continue my review with the
remaining commits, but when I'm done, could you please submit a v11,
taking into account my comments?

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 15 May 2025 07:22:13 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 03:22:13 2025
Received: from localhost ([127.0.0.1]:50501 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFSuz-000252-9c
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 03:22:13 -0400
Received: from mail-pl1-x632.google.com ([2607:f8b0:4864:20::632]:59603)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFSuu-00024l-LO
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 03:22:09 -0400
Received: by mail-pl1-x632.google.com with SMTP id
 d9443c01a7336-22e4d235811so7804265ad.2
 for <76081 <at> debbugs.gnu.org>; Thu, 15 May 2025 00:22:08 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747293722; x=1747898522; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=1t3rbwdEY/hFpl9ghLOPKLi/SuEGxwLfbSrjYxfwkh0=;
 b=EtYYvz1ywbMLLvypgaG3YwYA/qeFXq1H+1t0TvvW/f1VYH5H41Zb5QGXBparvgjPs5
 HJC5ixO84iXB08cntSZrui2M8jUppmCJGSxjaRBJ3x6+SPP33I3PcuGKn5d0xjchVZz3
 aTeYYBbAwut1AQpCXKTWuKdH5TWkIbDJ/z/3SCxAEzHDVWoWf+4zNOgu2o22TZxMqY7B
 sNoX61O7veyvyk0wRU2sazW/1h4b/dmUwdIM7rQ+Sro7OQaRmoqqQXpmGaqJu5X+zPnr
 AHfHkwNKxLcC0mUCtCy400SEhcduYx5jlamqBUBp2/2LZLlFvDsF4hW/qcUM2gI2E239
 mTaQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747293722; x=1747898522;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=1t3rbwdEY/hFpl9ghLOPKLi/SuEGxwLfbSrjYxfwkh0=;
 b=g7FfbeTk2oF1H52IxK0urDh1q4HIjEQw8eyuS0XtQFulzeVjNr2LQdJbPRvR+og8og
 G7M3piacpOQ4RiddD5dulXx9yxdzCARt1tW08P50oFz++2LC5sdAGyx/T0JlZ/f8MKwn
 hNG3LpCFdT9e8+SYFR30YNx1hnr0DDUwvtenULxYsvicNEchtEdCx7mHA3AYE3/i9c32
 9x/NVDCL/8Cd8ZVk0HKINezRxYbFKHUgOwJRmdoZ2X1LOnOI/0/Nuki5s73EzQqO/Jzs
 tuaemj2JNn60vHpVBjT7jdEO+YIVzc9fjT7lBMEyd8OH1Q7ynFf+owdzW6wvQoz3ZSjJ
 Xuzg==
X-Gm-Message-State: AOJu0YwqR+ngmzQkXrglcrh4+Epi4bgpbFN8WdTws6vaectHYvIkMwop
 TV8tq1nqUYg5lYpT8D/8nZ8QJDK+ojVG+pL5hhPbpNXnSgcpB4Gt1eYVCXkz
X-Gm-Gg: ASbGncv/O65a4rjXvFwbNnjie+AwfxKicJOg6i+yGcsvzWG/qg5lDublRpHmW6PbfH4
 33vJsxp6YFSoGHKPW3ib6e9GPcyyKTuGRp+qBp4YapcnI0WDhmBKWnoXX3IsXa8Lx9bq6ynIDE/
 KsN4MZbvMVhRSI1B56nGaCyCWkepZSNUx/8xY+EpC80AoNwvfOE8LPq8w8gNOPl+fNmGCVFobOo
 Z2Z1++Tvx7/Ca8JSH26RON7hbFi2CfH7xlE4FrrWHrkqPy4Z5v5WixLOz5nZHQX8MXuD6vYpsZL
 gXEvd0MlLTMHqTyzmXGranwWuu3kJamUXRpNCbMCjIzX5+Rf1Q==
X-Google-Smtp-Source: AGHT+IG9eRA1s1Ch4W5sORX2zP+JinOhbAV9rv2Fe87zSoKCE1YoAvY/woEoO8JCIxTiJFEo6yjqpA==
X-Received: by 2002:a17:902:e747:b0:224:a96:e39 with SMTP id
 d9443c01a7336-2319810e266mr85342925ad.9.1747293721725; 
 Thu, 15 May 2025 00:22:01 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 d9443c01a7336-22fc82706ccsm111059975ad.109.2025.05.15.00.22.00
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Thu, 15 May 2025 00:22:01 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 3/7] tests: oci-container: Set explicit
 timeouts.
In-Reply-To: <b129e5ea7e6d09888d784f4622c3ad6690e6e8eb.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:50 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <b129e5ea7e6d09888d784f4622c3ad6690e6e8eb.1746431874.git.goodoldpaul@HIDDEN>
Date: Thu, 15 May 2025 16:21:58 +0900
Message-ID: <87r00qibjd.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> * gnu/tests/docker.scm: Simplify %test-oci-container test case and add
> explicit timeouts to tests outcomes.

[...]

>            (test-assert "docker-guile running"
>              (marionette-eval
>               '(begin
>                  (use-modules (gnu services herd))
> -                (match (start-service 'docker-guile)
> -                  (#f #f)
> -                  (('service response-parts ...)
> -                   (match (assq-ref response-parts 'running)
> -                     ((pid) pid)))))
> +                (wait-for-service 'docker-guile #:timeout 120)
> +                #t)

You can drop the trailing '#t'.

>               marionette))
>  
> -          (test-equal "passing host environment variables and volumes"
> -            '("value" "hello")
> -            (marionette-eval
> -             `(begin
> -                (use-modules (ice-9 popen)
> -                             (ice-9 rdelim))
> -
> -                (define slurp
> -                  (lambda args
> -                    (let* ((port (apply open-pipe* OPEN_READ args))
> -                           (output (let ((line (read-line port)))
> -                                     (if (eof-object? line)
> -                                         ""
> -                                         line)))
> -                           (status (close-pipe port)))
> -                      output)))
> -                (let* ((response1 (slurp
> -                                   ,(string-append #$docker-cli "/bin/docker")
> -                                   "exec" "docker-guile"
> -                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
> -                       (response2 (slurp
> -                                   ,(string-append #$docker-cli "/bin/docker")
> -                                   "exec" "docker-guile"
> -                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
> +          (test-assert "passing host environment variables and volumes"
> +            (begin
> +              (define (run-test)
> +                (marionette-eval
> +                 `(begin
> +                    (use-modules (ice-9 popen)
> +                                 (ice-9 rdelim))
> +
> +                    (define slurp
> +                      (lambda args
> +                        (let* ((port (apply open-pipe* OPEN_READ args))
> +                               (output (let ((line (read-line port)))
> +                                         (if (eof-object? line)
> +                                             ""
> +                                             line)))
> +                               (status (close-pipe port)))
> +                          output)))
> +                    (let* ((response1 (slurp
> +                                       ,(string-append #$docker-cli "/bin/docker")
> +                                       "exec" "docker-guile"
> +                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
> +                           (response2 (slurp
> +                                       ,(string-append #$docker-cli "/bin/docker")
> +                                       "exec" "docker-guile"
> +                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))

Please mind the 80 columns maximum width :-).

>  (display (call-with-input-file \"/shared.txt\" read-line)))")))
> -                  (list response1 response2)))
> -             marionette))
> +                      (list response1 response2)))
> +                 marionette))
> +              ;; Allow services to come up on slower machines

Please use complete punctuation for standalone comments.

> +              (let loop ((attempts 0))
> +                (if (= attempts 60)
> +                    (error "Service didn't come up after more than 60 seconds")

I'm curious as to why this is necessary, given in the previous test we
wait for the docker-guile service to be ready?

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 15 May 2025 04:54:26 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 00:54:26 2025
Received: from localhost ([127.0.0.1]:49326 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFQby-0001G0-7C
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 00:54:26 -0400
Received: from mail-pl1-x62a.google.com ([2607:f8b0:4864:20::62a]:51215)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFQbv-0001FI-Aa
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 00:54:23 -0400
Received: by mail-pl1-x62a.google.com with SMTP id
 d9443c01a7336-22c33677183so4571165ad.2
 for <76081 <at> debbugs.gnu.org>; Wed, 14 May 2025 21:54:23 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747284857; x=1747889657; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=zsANYmZBsOl6O7Iao6isXFdhI85ZgG7r7nVXTMeKyK4=;
 b=EdsheELp8CXPcD9xbz4qlUnxDb+i05FtFkwlmftAUtZYr67IVEwXZiyIYxBl+JY3vo
 xKTl9Zn+HLHIj9L6ff9TJHYBneW64tr+TIHy0/odong6yihtH1cd5xa/YOPdZuwlsWGm
 OVAmJrwIGbYk4CQi+XgEE3xyo14T/PRk3VyxP2Fg7r/+fiHjkbMiIH03UaF/rvdCJRZg
 hcYE40FXgGj9EdtF+ZYvx4V7YdPHQIKXVooOaOXVr3/ny9Xj37NgLee84XKRjtoMEeLJ
 YybhyToJY3lVIrZbMr8IKvNI4Vr/1loIG7rwXqV5qpw4oJV0KdpnybSusWvolEsn3atS
 6brw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747284857; x=1747889657;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=zsANYmZBsOl6O7Iao6isXFdhI85ZgG7r7nVXTMeKyK4=;
 b=CKCX61KoiRsHjOne0sfJOTOOLI/oDAq0dhRdJVMJsqbisy3FcGvMXPjCWgTeTMwIoc
 6DIfDCu3OhYemZMV9DedDDnaVpKUMeVrw5MVAKm3CSSKdR6CEtbmKQImxipvTqKJe0l8
 aHhgPybVn84YZZ8Oi0/CIgF2pgX13fqkDu1AXzTd3pSchX2HZG0MDzfIkPvfhd8hbWvm
 t8YcfnLv52tJiAisSFcQtX4tu4YO+wRffDBa9ZAaDn1xfr8cWhvibsfkfU7ic1UzLMDD
 PAmCWBQ5kzKkIBNTVHumu91JEkNi++dNQrbQMTAqWL7LPadL21QKMw2PHCvfEWviEc81
 SxCg==
X-Gm-Message-State: AOJu0YxiQGkeYTwdHtu68U3TWBfXvNOd/V1KACWU/Z+nuueVELldn9F+
 7H+r7jG7VWV14n+cVx9vlmvIbCPLdBoHmDxpz8oNd8SHHxxmXbheHyfiEPNh
X-Gm-Gg: ASbGncsUOIyRlW/ki2cxTA+n/r6b+2EkIzH8gp1ph//B2mL6xfLgspL49Q2hXjHSRmy
 LGyZKKYO7bR5n+QTYLyPOZhUep82d4+Pb8JccmdX+qWECLEKMGnn/C0JlOocDyS6z9N7Bjgpppd
 woFUKdWQiktUpGd4qOyHG8p4of5fRWyhXPsAstQcDDze4IbZ5bCDMfwhk4mmjljI/49EWFXiCLh
 Q0KzydHK5Z4JzwvdtbHSfsqQVrdvNlx4sOPsKh8QCGu2mMSyp/OKZKg6q6nUPHDMI7ztMxXvXFA
 eQjFxBnG0Rej0m5il4QeVdSgxhtqbIN999TTN3LflCD1S4CSkg==
X-Google-Smtp-Source: AGHT+IHk9SJwx+aJT6BX+EchF7AiFrXL6Fiw1z1D8chTut2DDNzm0EsNqseQhH0pU8G8bgFbvhILAQ==
X-Received: by 2002:a17:903:1744:b0:21b:d2b6:ca7f with SMTP id
 d9443c01a7336-231b5ea080dmr14732855ad.32.1747284856831; 
 Wed, 14 May 2025 21:54:16 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 d9443c01a7336-22fc75490f8sm107484145ad.42.2025.05.14.21.54.15
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Wed, 14 May 2025 21:54:16 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 2/7] services:
 oci-container-configuration: Move to (gnu services containers).
In-Reply-To: <b4dd6bb5064d097efd8bb28a8c77b2072c8749f3.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:49 +0200")
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 <b4dd6bb5064d097efd8bb28a8c77b2072c8749f3.1746431874.git.goodoldpaul@HIDDEN>
Date: Thu, 15 May 2025 13:54:14 +0900
Message-ID: <874ixmjwy1.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> This patch moves the oci-container-configuration and related
> configuration records to (gnu services containers).
> Public symbols are still exported for backwards
> compatibility but since the oci-container-service-type will be
> deprecated in favor of the more general oci-service-type, everything is
> moved outside of the docker related module.
>
> * gnu/services/docker.scm: Move everything related to oci-container-configuration
> to...
> * gnu/services/containers.scm: ...here.scm.

This one also.

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 15 May 2025 04:54:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu May 15 00:54:09 2025
Received: from localhost ([127.0.0.1]:49320 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uFQbg-0001El-Kq
	for submit <at> debbugs.gnu.org; Thu, 15 May 2025 00:54:09 -0400
Received: from mail-pf1-x42c.google.com ([2607:f8b0:4864:20::42c]:50496)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <maxim.cournoyer@HIDDEN>)
 id 1uFQbf-0001Du-Bz
 for 76081 <at> debbugs.gnu.org; Thu, 15 May 2025 00:54:07 -0400
Received: by mail-pf1-x42c.google.com with SMTP id
 d2e1a72fcca58-73712952e1cso527342b3a.1
 for <76081 <at> debbugs.gnu.org>; Wed, 14 May 2025 21:54:07 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1747284841; x=1747889641; darn=debbugs.gnu.org;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to;
 bh=aZ63FHPiiPdPBAc50s5EY0M/gLJK6ngiyYYYr6Wtux4=;
 b=ESUMCPp7eQL9LKRZfFBV3urSzkRzb6C6E1jTGLbrczTB/QJ5mQuMVfWR/F1y0K95rB
 16ob9Aat4sSdAklzL8MgNk80wD40xavx6fQKCuiMmnUgpYxTQ5A0L8KueynYvLgu3MQT
 sL1SpvV2jsf2PX/+0va1rELVeDn8Bcn/RPkDaDabo7SJVdvT0PXc3fcHs8I7/3+EAyuU
 nsLFg90YF2rN7QQ4oK2kaInSl8aLeer99GUS07yXaKTsf4ElTcZhjQvRf85/mggJMWSw
 dSPQotNPvNOK27/JfeIYz4hzpjWw4Vs9wI8Vl80W/VRHgmOY/PNEZbMsPVclklEW9hvR
 GCZQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1747284841; x=1747889641;
 h=mime-version:user-agent:message-id:date:references:in-reply-to
 :subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date
 :message-id:reply-to;
 bh=aZ63FHPiiPdPBAc50s5EY0M/gLJK6ngiyYYYr6Wtux4=;
 b=WTWp5seuPxaSIR+NYX7a1pt6QUC/jNM91NvoH18ffyBKxT8Bwts0j1o9MBhqyw8zCL
 HhCPx9V8OJ+2H3HXZIRFeRcFjW9oulXTQpLoLKpHJqnTEKRhFLdo3UfuZ4m5PoidNiY+
 ppvSy3qBXaJS4+0eN3swAAKaNq5lzNcIt7NrLLhRVJtKLdLx/YgWEw+bu4LxMUF8ZxFJ
 DdMlEEkWLwnS/48YwYxnwRcu5bW60lEkp8z5WoUjsgUmk4AOmNkGBBYXzAF3kiXxJ+vY
 oTRqH27nXO6JXTtKQxFHzrzcVJONim6njxz+sieyiwkmdtjbTmGnNQhSNoOeO4GIfeD6
 2aaA==
X-Gm-Message-State: AOJu0Yx+wbV8BnyGc20twQaHPvrHbnHfBhcMCSSSO0wpVlymyWhwCiGl
 tl+Hckm8uWSQcaQHlSZ7hn64MEbDukKy8w7SysNY9JJtEdXFq8UNT0bawY0v
X-Gm-Gg: ASbGncvDc7Q1lNt9Sw9//qQe6RSSxRUqfEX1LwSLXBi9dlRx7yJa4W85bzWyWm5dIzi
 tWX0TF249LZfmcMi0PvEZx/ZCSuZtrmHgzNOJjRvNiPEpZ4HOOlCGvXSewlgLewWBEKSURbGXKZ
 RKZViIwle+B6qu5iu1q4Qd7KecLIU3Lt4Rwz5y1mpTmeQ+YFRF/UZCgqYNmRh33DIpLCE7r/vKQ
 o2kjZCRab1LfcjCm7Pg+j9SO2AVxhBlKPs4iqrSTyYtDw5k8Glakz1GhKaD7FGWNWTj137NTGKz
 QWHA6wIz7NaUwBEnQCOmJJVnRcs/Rkavawx6PwnWUXvVWb3Vcg==
X-Google-Smtp-Source: AGHT+IHvLC/JfWLd3NvxR3cpY4N1z+RMh+a4ljntJQiQiPdfBAjLphoKpPGlZbWPsKLJwpt2WLxqzQ==
X-Received: by 2002:a05:6a21:6da3:b0:201:2834:6c62 with SMTP id
 adf61e73a8af0-216115589f9mr1392863637.25.1747284840717; 
 Wed, 14 May 2025 21:54:00 -0700 (PDT)
Received: from terra ([2405:6586:be0:0:83c8:d31d:2cec:f542])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-74237a0f14csm10681744b3a.107.2025.05.14.21.53.59
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Wed, 14 May 2025 21:54:00 -0700 (PDT)
From: Maxim Cournoyer <maxim.cournoyer@HIDDEN>
To: Giacomo Leidi <goodoldpaul@HIDDEN>
Subject: Re: [bug#76081] [PATCH v10 1/7] services: rootless-podman: Use
 login shell.
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
 (Giacomo Leidi's message of "Mon, 5 May 2025 09:57:48 +0200")
References: <2f43e635-508c-407a-8309-06e75d492d89@HIDDEN>
 <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
Date: Thu, 15 May 2025 13:53:57 +0900
Message-ID: <878qmyjwyi.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13)
MIME-Version: 1.0
Content-Type: text/plain
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: 76081 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

Giacomo Leidi <goodoldpaul@HIDDEN> writes:

> This commit allows for having PATH set when changing the owner of
> /sys/fs/group.
>
> * gnu/services/containers.scm (crgroups-fs-owner): Use login shell.
>
> Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
> ---
>  gnu/services/containers.scm | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
> index b3cd109ce6c..d5a211765a6 100644
> --- a/gnu/services/containers.scm
> +++ b/gnu/services/containers.scm
> @@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
>      (rootless-podman-configuration-group-name config))
>    (program-file "cgroups2-fs-owner-entrypoint"
>                  #~(system*
> -                   (string-append #+bash-minimal "/bin/bash") "-c"
> +                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
>                     (string-append "echo Setting /sys/fs/cgroup "
>                                    "group ownership to " #$group " && chown -v "
>                                    "root:" #$group " /sys/fs/cgroup && "
>
> base-commit: 63088c295d81cc3d0e808c478d4fe479a2c90102

I pushed this one.

-- 
Thanks,
Maxim




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:25 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:25 2025
Received: from localhost ([127.0.0.1]:38286 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiW-0008TM-ES
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:25 -0400
Received: from confino.investici.org ([93.190.126.19]:51341)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiI-0008Rp-Ni
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:13 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431889;
 bh=2rKDRRmVHhOae7rEY88xph6nJ6AWx2gQOUzmsOUtq0A=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=tbi/gSQQ3QnTkImDAoyMLVxMAshSmXvgljlDD9/AL802DbG/vavfHgj/+4zqfta3b
 Xryr+Qm2QAg2rS0MuETz3WFt9AlOcuhmkFAdzrmOWK+YG+KmN1JUnPwb8fq1hJ6Fiw
 YY/lIWHPRpBruAIGLgOf3NNkinaIrlnord4yZTOg=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmd2sS5z11L0;
 Mon,  5 May 2025 07:58:09 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmd1dhHz11Bx; Mon,  5 May 2025 07:58:09 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 6/7] services: oci: Migrate oci-configuration to (guix
 records).
Date: Mon,  5 May 2025 09:57:53 +0200
Message-ID: <1f148e2994e78ed4614efe885380c06740515d0b.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit migrates oci-configuration to (guix records) singe it
appears (for-home (oci-configuration ...)) does not work as expected
with (gnu services configuration).  This is supposed to be completely
transparent for users and can be reverted in the
future once this has been implemented.

* gnu/service/containers.scm: Migrate oci-configuration to (guix records).
---
 gnu/services/containers.scm | 199 +++++++++++++++++++++---------------
 1 file changed, 117 insertions(+), 82 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index a8d10d842da..a974227e164 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,6 +39,7 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -168,6 +169,7 @@ (define-module (gnu services containers)
             oci-container-shepherd-service
             oci-objects-merge-lst
             oci-extension-merge
+            oci-service-extension-wrap-validate
             oci-service-type
             oci-service-accounts
             oci-service-profile
@@ -395,7 +397,7 @@ (define (oci-runtime-name runtime)
 (define (oci-runtime-group runtime maybe-group)
   "Implement the logic behind selection of the group that is to be used by
 Shepherd to execute OCI commands."
-  (if (not (maybe-value-set? maybe-group))
+  (if (eq? maybe-group #f)
       (if (eq? 'podman runtime)
           "cgroup"
           "docker")
@@ -766,62 +768,74 @@ (define (list-of-oci-networks? value)
 (define (package-or-string? value)
   (or (package? value) (string? value)))
 
-(define-maybe/no-serialization package-or-string)
-
-(define-configuration/no-serialization oci-configuration
-  (runtime
-   (symbol 'docker)
-   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
-@code{'podman}."
-   (sanitizer oci-sanitize-runtime))
-  (runtime-cli
-   (maybe-package-or-string)
-   "The OCI runtime command line to be installed in the system profile and used
-to provision OCI resources, it can be either a package or a string representing
-an absolute path to the runtime binary entrypoint.  When unset it will default
-to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
-package for the @code{'podman} runtime.")
-  (runtime-extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be placed
-after each @command{docker} or @command{podman} invokation.")
-  (user
-   (string "oci-container")
-   "The user name under whose authority OCI runtime commands will be run.")
-  (group
-   (maybe-string)
-   "The group name under whose authority OCI commands will be run.  When
-using the @code{'podman} OCI runtime, this field will be ignored and the
-default group of the user configured in the @code{user} field will be used.")
-  (subuids-range
-   (maybe-subid-range)
-   "An optional @code{subid-range} record allocating subuids for the user from
-the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
-defaults to @code{(subid-range (name \"oci-container\"))}.")
-  (subgids-range
-   (maybe-subid-range)
-   "An optional @code{subid-range} record allocating subgids for the user from
-the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
-defaults to @code{(subid-range (name \"oci-container\"))}.")
-  (containers
-   (list-of-oci-containers '())
-   "The list of @code{oci-container-configuration} records representing the
-containers to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (networks
-   (list-of-oci-networks '())
-   "The list of @code{oci-network-configuration} records representing the
-networks to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (volumes
-   (list-of-oci-volumes '())
-   "The list of @code{oci-volume-configuration} records representing the
-volumes to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (verbose?
-   (boolean #f)
-   "When true, additional output will be printed, allowing to better follow the
-flow of execution."))
+;; (for-home (oci-configuration ...)) is not able to replace for-home? with #t,
+;; pk prints #f. Once for-home will be able to work with (gnu services configuration) the
+;; record can be migrated back to define-configuration.
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+;; TODO: This procedure can be dropped once we switch to define-configuration for
+;; oci-configuration.
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
 
 (define (oci-runtime-system-environment runtime user)
   (if (eq? runtime 'podman)
@@ -837,7 +851,7 @@ (define (oci-runtime-cli runtime runtime-cli path)
       ;; It is a user defined absolute path
       runtime-cli
       #~(string-append
-         #$(if (not (maybe-value-set? runtime-cli))
+         #$(if (eq? runtime-cli #f)
                path
                runtime-cli)
          #$(if (eq? 'podman runtime)
@@ -1581,18 +1595,27 @@ (define (oci-configuration->shepherd-services config)
                   (passwd:gid
                    (getpwnam #$user))))
               (oci-runtime-group config (oci-configuration-group config))))
-         (verbose? (oci-configuration-verbose? config)))
-    (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
-                                  #:user user
-                                  #:group group
-                                  #:verbose? verbose?
-                                  #:runtime-extra-arguments
-                                  runtime-extra-arguments
-                                  #:runtime-environment
-                                  (oci-runtime-system-environment runtime user)
-                                  #:runtime-requirement
-                                  (oci-runtime-system-requirement runtime)
-                                  #:networks-requirement '(networking))))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group group
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
 
 (define (oci-service-subids config)
   "Return a subids-extension record representing subuids and subgids required by
@@ -1620,14 +1643,14 @@ (define (oci-service-subids config)
   (define subgid-ranges
     (delete-duplicate-ranges
      (cons
-      (if (not (maybe-value-set? subgids))
+      (if (eq? subgids #f)
           (subid-range (name user))
           subgids)
       container-users)))
   (define subuid-ranges
     (delete-duplicate-ranges
      (cons
-      (if (not (maybe-value-set? subuids))
+      (if (eq? subuids #f)
           (subid-range (name user))
           subuids)
       container-users)))
@@ -1686,13 +1709,21 @@ (define (oci-service-profile runtime runtime-cli)
           '()
           (list
            (cond
-            ((maybe-value-set? runtime-cli)
+            ((not (eq? runtime-cli #f))
              runtime-cli)
             ((eq? 'podman runtime)
              podman)
             (else
              docker-cli))))))
 
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
 (define (oci-configuration-extend config extension)
   (oci-configuration
    (inherit config)
@@ -1721,18 +1752,22 @@ (define oci-service-type
                 (extensions
                  (list
                   (service-extension profile-service-type
-                                     (lambda (config)
-                                       (let ((runtime-cli
-                                              (oci-configuration-runtime-cli config))
-                                             (runtime
-                                              (oci-configuration-runtime config)))
-                                         (oci-service-profile runtime runtime-cli))))
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
                   (service-extension subids-service-type
-                                     oci-service-subids)
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
                   (service-extension account-service-type
-                                     oci-service-accounts)
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
                   (service-extension shepherd-root-service-type
-                                     oci-configuration->shepherd-services)))
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
                 ;; Concatenate OCI object lists.
                 (compose (lambda (args)
                            (fold oci-extension-merge
-- 
2.49.0





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:24 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:24 2025
Received: from localhost ([127.0.0.1]:38284 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiT-0008TC-SE
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:24 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:40745)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiG-0008Rc-FB
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:13 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431887;
 bh=gLbNxX/Rr463F7DB8fOCHxdTIo0Xof4vZccQyml0Dh4=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=A2F5HrH9hm4zG5V9ff/bj0xxfn/SfAf3vHTb2LfdQ6ZpNNX+FDdlDra3vcW3Il5br
 r18/l9ADuMnGCFHWVW1BfcSR17BgZD3NTCIyeRhsE5HdiZ9KYz4TlD5PcAvDION2aI
 UYBcQO/nuXfvaJgGTGb/udIS1CE7LcJ1zfi/5S1w=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmb47lDz11Kk;
 Mon,  5 May 2025 07:58:07 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmb2bBcz1193; Mon,  5 May 2025 07:58:07 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 2/7] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Mon,  5 May 2025 09:57:49 +0200
Message-ID: <b4dd6bb5064d097efd8bb28a8c77b2072c8749f3.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 2 files changed, 584 insertions(+), 542 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -190,7 +239,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -309,495 +297,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invocation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
-- 
2.49.0





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:22 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:21 2025
Received: from localhost ([127.0.0.1]:38282 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiT-0008T6-3R
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:21 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:38291)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiI-0008Rs-Nh
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:12 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431889;
 bh=GjSoThHGAjYqXV22ZsTYGAbSuVZWgm8ExtgG8eE1E4U=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=YA3FMFX4WjnYZHpF+LOwkkqq8T3+dYKEyxJekNntLpIUbYI7uY7H+bsHdWWhRCMcU
 IbGM0UFae2tqDaVdGrwos2rDAi0b2hmzLh6qopnV3YD++qRvzRN+0v7cydUNzqr6lT
 5vYB/oP2Z6m6pPgI8DyyxJN8i/dsN7vyOhm/T/9o=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmd5vXNz1193;
 Mon,  5 May 2025 07:58:09 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmd4gSdz11Bx; Mon,  5 May 2025 07:58:09 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 7/7] home: Add home-oci-service-type.
Date: Mon,  5 May 2025 09:57:54 +0200
Message-ID: <91c9920430684861541c86a01c28bd46423102c8.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Gabriel Wicki <gabriel@HIDDEN>, Hilton Chain <hako@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 3 files changed, 165 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index de0d7ccfd5a..3f4da28af72 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -51429,6 +51429,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index e25fcc115be..1be38d6e217 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -103,6 +103,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
-- 
2.49.0





Information forwarded to andrew@HIDDEN, gabriel@HIDDEN, hako@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:21 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:21 2025
Received: from localhost ([127.0.0.1]:38280 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiS-0008Sy-6b
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:20 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:24233)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiH-0008Rg-KO
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:11 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431888;
 bh=ARfDk5ZxRNAIn3v/DozOaqIGhEwX6nP3gYi8qoILsBk=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=LLWJ68lFKr+GtBGCs3jCdkkq9Y6brOo4MPZ2S115fgCJ4mY/TPYkRkpv1ROL+JQN7
 nIFlrPlgYQr7M/R1685VCMxb0CbLeU21JyDgdIMVUo9qZP7+TFEAubTUJKUCWsVRDz
 ITTJJsUJbg8hf7LgX6fAsI/x33jLQ63w3xEAPlNo=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmc5y5Hz11L3;
 Mon,  5 May 2025 07:58:08 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmc4sBdz11L0; Mon,  5 May 2025 07:58:08 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 5/7] tests: Use lower-oci-image-state in container tests.
Date: Mon,  5 May 2025 09:57:52 +0200
Message-ID: <cd5b10a3f3ecb770539718e8d6db11362087d73d.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 66b46456800..a8d10d842da 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.49.0





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:20 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:20 2025
Received: from localhost ([127.0.0.1]:38278 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiR-0008Ss-Fn
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:20 -0400
Received: from confino.investici.org ([93.190.126.19]:60307)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiG-0008Re-SE
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:11 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431887;
 bh=Ub8YhXpnOQtdTL87qKNJEOV082EWZgVgJr5FfqgBc9c=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=N2Q73GVTjfl9geBSZ4jxfvlYpJINrwHCjsj7zWaC5//vmkLOjCcmslecrsSJfVLDv
 eBEdEb3n9NEe1jMfSFH1J2MaXMN07CGKGXadWfdpwr8rfO6h5YLnXLM680MC5ZfmSs
 9IernMBKxWNxfZpe8dLyt+Gfw681k35mpaVfhonU=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmb6rCCz11Kq;
 Mon,  5 May 2025 07:58:07 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmb5k31z1193; Mon,  5 May 2025 07:58:07 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 3/7] tests: oci-container: Set explicit timeouts.
Date: Mon,  5 May 2025 09:57:50 +0200
Message-ID: <b129e5ea7e6d09888d784f4622c3ad6690e6e8eb.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

* gnu/tests/docker.scm: Simplify %test-oci-container test case and add
explicit timeouts to tests outcomes.
---
 gnu/tests/docker.scm | 99 ++++++++++++++++++--------------------------
 1 file changed, 41 insertions(+), 58 deletions(-)

diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.49.0





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:13 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:13 2025
Received: from localhost ([127.0.0.1]:38275 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiK-0008SF-7g
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:13 -0400
Received: from confino.investici.org ([93.190.126.19]:42627)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiG-0008Rb-AF
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:09 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431887;
 bh=msLIwlh/X6UfUT74UbhGpu/nj1vszwErxmhdqMBU7UA=;
 h=From:To:Cc:Subject:Date:From;
 b=SNiYJ1Gk38jx3tC/SrWaAY2WqacI6X8yj4L3BvD5sUCQasl4q6bW01a2pKHrbi62h
 gBDMQlLXkUOOiLafZ5GGdSbyEhryuOh6EPHK7ky8FEt1MjUMbV7hgwPAX2Z05vJ3E9
 MEWJuLORTHRk24KKFLIvgxJguULtmqmy5/+yrgSA=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmb11TGz11Kf;
 Mon,  5 May 2025 07:58:07 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmZ6v1nz1193; Mon,  5 May 2025 07:58:06 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 1/7] services: rootless-podman: Use login shell.
Date: Mon,  5 May 2025 09:57:48 +0200
Message-ID: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index b3cd109ce6c..d5a211765a6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 63088c295d81cc3d0e808c478d4fe479a2c90102
-- 
2.49.0





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:58:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:58:12 2025
Received: from localhost ([127.0.0.1]:38274 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqiK-0008SD-41
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:12 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:31959)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqiH-0008Rf-Ar
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:58:09 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431888;
 bh=ZbdJrvwQ0Hv4rn+cK8CEMo0tdneqg17+nFkPZm15q3w=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=nmM2Ow4oHIp72NAcWEMYHDTSlMLTg1nOqaEpfzUN8jw1HFhfIxNyUmF/goveonT0G
 vYdwkgcwfDYDuec/pPIYegRW7OajBnAORaGKTkKx4qFk4l8TRoI1CHU7sC3ByhkoHG
 ukqk8Jgk9lXLAAYWMHUFvmYYp68xAxZXQsOKJbQY=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYmc3Bn4z1193;
 Mon,  5 May 2025 07:58:08 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYmc1rG8z11L0; Mon,  5 May 2025 07:58:08 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v10 4/7] services: Add oci-service-type.
Date: Mon,  5 May 2025 09:57:51 +0200
Message-ID: <e3227ad2c6019866db8f7f6e59652ecaec2b7b6f.1746431874.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.49.0
In-Reply-To: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
References: <2b78b4ce9b0a3a6c0bdbdec5bb16702a5b5083a3.1746431874.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Gabriel Wicki <gabriel@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 ++++++--
 gnu/services/containers.scm | 1304 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 ++++++++++++++++++++++++++-
 4 files changed, 2394 insertions(+), 252 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 7f796c5fc94..de0d7ccfd5a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -43782,59 +43782,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources, it can be either a package or a string representing
+an absolute path to the runtime binary entrypoint.  When unset it will default
+to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
+package for the @code{'podman} runtime.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -43854,16 +43957,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -43871,22 +43974,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -43914,7 +44019,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -43925,10 +44030,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -43939,25 +44045,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 24f31c756b8..66b46456800 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,82 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
+            oci-container-configuration->options
+            oci-network-configuration->options
+            oci-volume-configuration->options
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -296,9 +371,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (not (maybe-value-set? maybe-group))
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -347,6 +455,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -374,10 +487,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -392,7 +510,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -437,11 +567,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -451,9 +585,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -477,15 +611,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -514,7 +649,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -526,9 +661,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -541,71 +677,348 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invocation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define-maybe/no-serialization package-or-string)
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package-or-string)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources, it can be either a package or a string representing
+an absolute path to the runtime binary entrypoint.  When unset it will default
+to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
+package for the @code{'podman} runtime.")
+  (runtime-extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (not (maybe-value-set? runtime-cli))
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invocation)
+  "Return a Shepherd action printing a given INVOCATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invocation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invocation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -614,24 +1027,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -642,14 +1042,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -662,7 +1059,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -677,113 +1075,669 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invocation runtime runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invocation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run" "--rm"
+    ,@(if (eq? runtime 'podman)
+          ;; This is because podman takes some time to
+          ;; release container names.  --replace seems
+          ;; to be required to be able to restart services.
+          '("--replace")
+          '())
+    "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invocation #:key (verbose? #f) (pre-script #~()))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%")
+         (format #t "Current user: ~a ~a~%"
+                 (getuid) (passwd:name (getpwuid (getuid))))
+         (format #t "Current group: ~a ~a~%"
+                 (getgid) (group:name (getgrgid (getgid))))
+         (format #t "Current directory ~a~%" (getcwd)))
+       (define invocation (list #$@invocation))
+       #$@pre-script
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invocation))
+       (apply execlp `(,(first invocation) ,@invocation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invocation
+          (oci-container-run-invocation
+           runtime runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments))
+         (container-action
+          (lambda* (command #:key (environment-variables #f))
+            #~(lambda _
+                (fork+exec-command
+                 (list #$@command)
+                 #$@(if user (list #:user user) '())
+                 #$@(if group (list #:group group) '())
+                 #$@(if (maybe-value-set? log-file)
+                        (list #:log-file log-file)
+                        '())
+                 #$@(if (and user (eq? runtime 'podman))
+                        (list #:directory
+                              #~(passwd:dir (getpwnam #$user)))
+                        '())
+                 #$@(if environment-variables
+                        (list #:environment-variables
+                              environment-variables)
+                        '()))))))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
+                       (container-action
+                        (list (oci-container-entrypoint
+                               runtime runtime-cli name image image-reference
+                               invocation #:verbose? verbose?
+                               #:pre-script
+                               (if (oci-image? image)
+                                   #~((system*
+                                       #$(oci-image-loader
+                                          runtime runtime-cli name image
+                                          image-reference #:verbose? verbose?)))
+                                   #~())))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                       (container-action
+                        (list
+                         (oci-container-entrypoint
+                          runtime runtime-cli name image image-reference
+                          (list runtime-cli "rm" "-f" name)
+                          #:verbose? verbose?))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invocation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 (container-action
+                                  (list
+                                   (oci-container-entrypoint
+                                    runtime runtime-cli service-name image
+                                    image-reference
+                                    (list runtime-cli "pull" image)
+                                    #:verbose? verbose?))
+                                  #:environment-variables
+                                  #~(append
+                                     (list #$@host-environment)
+                                     (list #$@runtime-environment))))))))
+                        actions)))))
+
+(define (oci-object-create-invocation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invocation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invocations invocations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOCATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invocations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invocations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invocation)
+          (define name (last invocation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invocation))
+                (let ((exit-code (status:exit-val (apply system* invocation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invocations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invocations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOCATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invocations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invocations invocations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (network)
+            (oci-object-create-invocation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (volume)
+            (oci-object-create-invocation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  "Returns a list of Shepherd services based on the input OCI state."
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group
+          (if (eq? runtime 'podman)
+              #~(group:name
+                 (getgrgid
+                  (passwd:gid
+                   (getpwnam #$user))))
+              (oci-runtime-group config (oci-configuration-group config))))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                  #:user user
+                                  #:group group
+                                  #:verbose? verbose?
+                                  #:runtime-extra-arguments
+                                  runtime-extra-arguments
+                                  #:runtime-environment
+                                  (oci-runtime-system-environment runtime user)
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime)
+                                  #:networks-requirement '(networking))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (not (maybe-value-set? subgids))
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (not (maybe-value-set? subuids))
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((maybe-value-set? runtime-cli)
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 828ceea313a..125e748bb0e 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -297,17 +302,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.49.0





Information forwarded to gabriel@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 May 2025 07:55:01 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon May 05 03:55:01 2025
Received: from localhost ([127.0.0.1]:38258 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1uBqfF-0008Ff-5M
	for submit <at> debbugs.gnu.org; Mon, 05 May 2025 03:55:01 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:56059)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1uBqf8-0008FH-17
 for 76081 <at> debbugs.gnu.org; Mon, 05 May 2025 03:54:58 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1746431690;
 bh=3FlANTOrBjQfXl5z8SzCQ6RzJRlnKGxoLHpZ4vGZNJU=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=AGdKTXrMKoPHdul0IiOC7fOK9bK6n5ndwKdhLUAKMlIBQvzAXM7GCI3uaMlBu0kOW
 6HTo+elam5FEOWP0AADaBwnOPO1/PhQuLBQvWKM5z83f6NUvbusdupApZcKfxFmS9W
 XHvu7YzTsRAbWkQkTHHCFMkLKSfkxhXBJ6M1Ces8=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZrYhp5Hhfz1193
 for <76081 <at> debbugs.gnu.org>; Mon,  5 May 2025 07:54:50 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZrYhp4f7qz1158
 for <76081 <at> debbugs.gnu.org>; Mon,  5 May 2025 07:54:50 +0000 (UTC)
Message-ID: <06196cdd-ac29-4a03-b09c-98aed5f85a39@HIDDEN>
Date: Mon, 5 May 2025 09:54:49 +0200
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
 <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
 <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
 <a559945e-925c-46f5-a591-19154712b269@HIDDEN>
 <afa88170-8a5e-42a3-b024-caeef44da51f@HIDDEN>
 <020bb939-819c-4de3-bc04-ef0f33855c90@HIDDEN>
Content-Language: en-US
In-Reply-To: <020bb939-819c-4de3-bc04-ef0f33855c90@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

On 3/18/25 18:50, paul wrote:
> Hi,
>
> On 3/9/25 01:27, paul wrote:
>> Hi,
>>
>> On 3/4/25 13:39, paul wrote:
>>> Hi,
>>>
>>> On 2/27/25 00:01, paul wrote:
>>>> Hi guix,
>>>>
>>>> On 2/18/25 02:20, paul wrote:
>>>>> Hi,
>>>>>
>>>>> On 2/12/25 02:09, paul wrote:
>>>>>> Hi guix,
>>>>>>
>>>>>> On 2/9/25 21:38, paul wrote:
>>>>>>> I'm sending a v3 fixing a bug in the merge algorithm for volumes 
>>>>>>> and networks.
>>>>>>>
>>>>>>> On 2/9/25 20:14, paul wrote:
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> I'm about to send a v2. v2 compared to the first revision 
>>>>>>>> features:
>>>>>>>>
>>>>>>>>
>>>>>>>> - it actually compiles all the times :) (rev 1 referenced 
>>>>>>>> oci-image too early for it to be working and generated a 
>>>>>>>> compile time error, if you recompiled it sometimes went away so 
>>>>>>>> I thought it was a problem of my setup. CI caught this)
>>>>>>>> - it allows more values to be overridden by eventual users of 
>>>>>>>> the Scheme API
>>>>>>>> - it allows passing extra arguments directly after each podman 
>>>>>>>> or docker invokation, allowing for example for overriding 
>>>>>>>> podman --root and similar options.
>>>>>>>>
>>>>>>>> All of these tests should pass:
>>>>>>>>
>>>>>>>> guix shell -D guix -CPW -- make check-system 
>>>>>>>> TESTS="oci-container oci-service-rootless-podman docker 
>>>>>>>> docker-system rootless-podman oci-service-docker"
>>>>>>>>
>>>>>> I'm sending a v4 changing slightly the image loader, the same 
>>>>>> tests as before are supposed to pass. Now the Home service [0] is 
>>>>>> working for me with rootless podman. I'll try it on different 
>>>>>> distros if I manage to.
>>>>>
>>>>> I'm sending a v5 implementing a Home service. The changes compared 
>>>>> to v4 are pretty trivial as the plumbing was already there, the 
>>>>> only downside is that I'm not able to use for-home? in 
>>>>> define-configuration, so I had to reimplement oci-configuration 
>>>>> with (guix records) and had to reimplement some validation (gnu 
>>>>> services configuration) would figure out magically.
>>>>
>>>> I'm sending  a v6. Compared to v5 it resolves the conflicts with 
>>>> master and it should fix the stop action for rootless podman backed 
>>>> services.
>>>
>>> I'm sending a v7 fixing the restart action for podman backed services.
>>
>> I'm sending a v8 fixing some further bugs wrt to the start and stop 
>> of podman backed services.
>
> In the hope of making the review easier, I'm sending a v9 that is 
> completely the same of v8 in terms of changes but with smaller 
> commits. Hopefully this makes it easier to review changes, please let 
> me know if I can do anything to ease the workload for reviewers of 
> this patch set.

I'm sending a v10 rebased on current master. The only difference with v9 
is that this revision exports the 
oci-{container,network,volume}-configuration->options procedures , that 
can be useful when writing services that extend the oci-service-type.


cheers,

giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:40 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:39 2025
Received: from localhost ([127.0.0.1]:43088 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub7F-0007ky-0F
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:39 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:44921)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6F-0007cf-KK
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:37 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320294;
 bh=D1NpWLth5uLaBEsr3PkyNDiQ76GkGhDklVUyzpvsoVQ=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=njfjEa5vbZwvHiOxIhv3spuEiEnDS5RUU3pwjXy+f+6VJcjlUsIqAscH3pFfvJq3O
 oCcKWFFLIwJpnZdUBW1QqiLiktRr44oBKQVgvZFy67VlDz+DDbcPCSN9jPDJam/mZw
 wDpzUe8dxAy8qiyBjICWTvR43aOCUDgkXv1fr4rQ=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCV3Cn5z11dw;
 Tue, 18 Mar 2025 17:51:34 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCV1vynz11cm; Tue, 18 Mar 2025 17:51:34 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 6/7] services: oci: Migrate oci-configuration to (guix
 records).
Date: Tue, 18 Mar 2025 18:51:11 +0100
Message-ID: <f9d50ef80bf873d0e51c1536e6607b545a68a316.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit migrates oci-configuration to (guix records) singe it
appears (for-home (oci-configuration ...)) does not work as expected
with (gnu services configuration).  This is supposed to be completely
transparent for users and can be reverted in the
future once this has been implemented.

* gnu/service/containers.scm: Migrate oci-configuration to (guix records).
---
 gnu/services/containers.scm | 199 +++++++++++++++++++++---------------
 1 file changed, 117 insertions(+), 82 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 5dac8e80f22..7a44e1c0f7c 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,6 +39,7 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -164,6 +165,7 @@ (define-module (gnu services containers)
             oci-container-shepherd-service
             oci-objects-merge-lst
             oci-extension-merge
+            oci-service-extension-wrap-validate
             oci-service-type
             oci-service-accounts
             oci-service-profile
@@ -391,7 +393,7 @@ (define (oci-runtime-name runtime)
 (define (oci-runtime-group runtime maybe-group)
   "Implement the logic behind selection of the group that is to be used by
 Shepherd to execute OCI commands."
-  (if (not (maybe-value-set? maybe-group))
+  (if (eq? maybe-group #f)
       (if (eq? 'podman runtime)
           "cgroup"
           "docker")
@@ -762,62 +764,74 @@ (define (list-of-oci-networks? value)
 (define (package-or-string? value)
   (or (package? value) (string? value)))
 
-(define-maybe/no-serialization package-or-string)
-
-(define-configuration/no-serialization oci-configuration
-  (runtime
-   (symbol 'docker)
-   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
-@code{'podman}."
-   (sanitizer oci-sanitize-runtime))
-  (runtime-cli
-   (maybe-package-or-string)
-   "The OCI runtime command line to be installed in the system profile and used
-to provision OCI resources, it can be either a package or a string representing
-an absolute path to the runtime binary entrypoint.  When unset it will default
-to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
-package for the @code{'podman} runtime.")
-  (runtime-extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be placed
-after each @command{docker} or @command{podman} invokation.")
-  (user
-   (string "oci-container")
-   "The user name under whose authority OCI runtime commands will be run.")
-  (group
-   (maybe-string)
-   "The group name under whose authority OCI commands will be run.  When
-using the @code{'podman} OCI runtime, this field will be ignored and the
-default group of the user configured in the @code{user} field will be used.")
-  (subuids-range
-   (maybe-subid-range)
-   "An optional @code{subid-range} record allocating subuids for the user from
-the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
-defaults to @code{(subid-range (name \"oci-container\"))}.")
-  (subgids-range
-   (maybe-subid-range)
-   "An optional @code{subid-range} record allocating subgids for the user from
-the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
-defaults to @code{(subid-range (name \"oci-container\"))}.")
-  (containers
-   (list-of-oci-containers '())
-   "The list of @code{oci-container-configuration} records representing the
-containers to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (networks
-   (list-of-oci-networks '())
-   "The list of @code{oci-network-configuration} records representing the
-networks to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (volumes
-   (list-of-oci-volumes '())
-   "The list of @code{oci-volume-configuration} records representing the
-volumes to provision.  Most users are supposed not to use this field and use
-the @code{oci-extension} record instead.")
-  (verbose?
-   (boolean #f)
-   "When true, additional output will be printed, allowing to better follow the
-flow of execution."))
+;; (for-home (oci-configuration ...)) is not able to replace for-home? with #t,
+;; pk prints #f. Once for-home will be able to work with (gnu services configuration) the
+;; record can be migrated back to define-configuration.
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+;; TODO: This procedure can be dropped once we switch to define-configuration for
+;; oci-configuration.
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
 
 (define (oci-runtime-system-environment runtime user)
   (if (eq? runtime 'podman)
@@ -833,7 +847,7 @@ (define (oci-runtime-cli runtime runtime-cli path)
       ;; It is a user defined absolute path
       runtime-cli
       #~(string-append
-         #$(if (not (maybe-value-set? runtime-cli))
+         #$(if (eq? runtime-cli #f)
                path
                runtime-cli)
          #$(if (eq? 'podman runtime)
@@ -1577,18 +1591,27 @@ (define (oci-configuration->shepherd-services config)
                   (passwd:gid
                    (getpwnam #$user))))
               (oci-runtime-group config (oci-configuration-group config))))
-         (verbose? (oci-configuration-verbose? config)))
-    (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
-                                  #:user user
-                                  #:group group
-                                  #:verbose? verbose?
-                                  #:runtime-extra-arguments
-                                  runtime-extra-arguments
-                                  #:runtime-environment
-                                  (oci-runtime-system-environment runtime user)
-                                  #:runtime-requirement
-                                  (oci-runtime-system-requirement runtime)
-                                  #:networks-requirement '(networking))))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group group
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
 
 (define (oci-service-subids config)
   "Return a subids-extension record representing subuids and subgids required by
@@ -1616,14 +1639,14 @@ (define (oci-service-subids config)
   (define subgid-ranges
     (delete-duplicate-ranges
      (cons
-      (if (not (maybe-value-set? subgids))
+      (if (eq? subgids #f)
           (subid-range (name user))
           subgids)
       container-users)))
   (define subuid-ranges
     (delete-duplicate-ranges
      (cons
-      (if (not (maybe-value-set? subuids))
+      (if (eq? subuids #f)
           (subid-range (name user))
           subuids)
       container-users)))
@@ -1682,13 +1705,21 @@ (define (oci-service-profile runtime runtime-cli)
           '()
           (list
            (cond
-            ((maybe-value-set? runtime-cli)
+            ((not (eq? runtime-cli #f))
              runtime-cli)
             ((eq? 'podman runtime)
              podman)
             (else
              docker-cli))))))
 
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
 (define (oci-configuration-extend config extension)
   (oci-configuration
    (inherit config)
@@ -1717,18 +1748,22 @@ (define oci-service-type
                 (extensions
                  (list
                   (service-extension profile-service-type
-                                     (lambda (config)
-                                       (let ((runtime-cli
-                                              (oci-configuration-runtime-cli config))
-                                             (runtime
-                                              (oci-configuration-runtime config)))
-                                         (oci-service-profile runtime runtime-cli))))
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
                   (service-extension subids-service-type
-                                     oci-service-subids)
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
                   (service-extension account-service-type
-                                     oci-service-accounts)
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
                   (service-extension shepherd-root-service-type
-                                     oci-configuration->shepherd-services)))
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
                 ;; Concatenate OCI object lists.
                 (compose (lambda (args)
                            (fold oci-extension-merge
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:37 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:37 2025
Received: from localhost ([127.0.0.1]:43086 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub7B-0007kJ-Ok
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:36 -0400
Received: from confino.investici.org ([93.190.126.19]:62429)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6F-0007cm-N7
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:37 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320294;
 bh=YIWVLEeDQajQFrSs7DsdCZ73N1HG9vBfN0Oi1S3Sg9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=Gb+khoiLm6q38CJBazFIE1Z+W08VeR32hYqXG6IJkITz8sFlBsaG1XTaeuB+2Kez6
 L3QvBDmrCwjtrfFr2XsS6jH+iuDbWkTP195/LTwFjaFwaP8oY/8nbMy0glbgr6z9/g
 BGYINanT35Jb6dAHuXypUGZYYvm1BJPqWwm69MFc=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCV61t1z11d4;
 Tue, 18 Mar 2025 17:51:34 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCV50PTz11cm; Tue, 18 Mar 2025 17:51:34 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 7/7] home: Add home-oci-service-type.
Date: Tue, 18 Mar 2025 18:51:12 +0100
Message-ID: <43bf916116776f60f35cfbce848540c43607c589.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 3 files changed, 165 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 521ea28dd5a..8f3017c2f69 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -50478,6 +50478,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index 01d13a11ae8..0f6502d979b 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -103,6 +103,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
-- 
2.48.1





Information forwarded to andrew@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:34 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:33 2025
Received: from localhost ([127.0.0.1]:43084 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub78-0007jo-7U
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:33 -0400
Received: from confino.investici.org ([93.190.126.19]:44949)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6D-0007cL-CG
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:36 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320290;
 bh=bWiorRi+IXwRYJjyNsYQ0mrdmTXS3QuGkZwZmxfXCB8=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=j1mzjNTbjOSrDN1xZM4frZdthoyflVNntMvXNa6My1YvYT5k+FEm2DDCB+uykhYIb
 posXVB3dlFQf3qOvdj/xLFeNsGtfqnl4YZXCV0djRqXjJTLR2SYyvzTRj5ML/VlKsl
 fPQCo3Ei2SIo4Zulvr85Ab0gy8iJdQVrOcD5C054=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCQ1C7tz11d3;
 Tue, 18 Mar 2025 17:51:30 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCP6xp6z11cm; Tue, 18 Mar 2025 17:51:29 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 2/7] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Tue, 18 Mar 2025 18:51:07 +0100
Message-ID: <d46b8563f47f625d62fb02a50b9a31c66f2a28c0.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 2 files changed, 584 insertions(+), 542 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -190,7 +239,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -309,495 +297,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invocation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:30 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:30 2025
Received: from localhost ([127.0.0.1]:43082 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub77-0007jl-G7
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:30 -0400
Received: from confino.investici.org ([93.190.126.19]:36385)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6D-0007cH-5k
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:33 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320289;
 bh=8gJyOKA+4fXouDtzt4hPNvCopbTmW/qpZs43sizr4d0=;
 h=From:To:Cc:Subject:Date:From;
 b=iEGeEnJ+XMxTjmr2KtCcmjGDmVMTHxWqhT/+BudnHatnlW4JHj1pA0/ilGXOHWEh+
 Q+L9mgZgA1EPLi28+4KrxGpzUlBwugvwbuEQaSDoAnSvGB8MTuoVOUj+GVBgwt9rxi
 BdcMpcGo2/nvDiBuHgHi1t+/VPBnthz68jAHx/M4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCP5HLhz11cn;
 Tue, 18 Mar 2025 17:51:29 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCP4C66z11cm; Tue, 18 Mar 2025 17:51:29 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 1/7] services: rootless-podman: Use login shell.
Date: Tue, 18 Mar 2025 18:51:06 +0100
Message-ID: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index b3cd109ce6c..d5a211765a6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 9879a7d75279762c7634f1a585794442ea2f0503
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:09 2025
Received: from localhost ([127.0.0.1]:43080 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub6m-0007hX-9Q
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:09 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:21841)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6F-0007ce-J2
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:36 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320294;
 bh=/3wm1tMXIgU4KT1kvz1WhzIRH1XipEQlymAUa9c0zZ8=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=lFedZCvGPkgHjcsAR0z27G6t1n3nv4bzB2H2gXNCS6SfavEsGEH+sz8vh68DeDMzD
 RbAV6oFqaKltZml6md40+4HV6Mo3DfO51I55uaaPHvV2MKUTmYEgAq9z9qgKcrxnbc
 Oz/oOEAbOTjSZKZW7tMg/z5BKYBXK9oKvwZ5b/Sk=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCV0LkGz11cn;
 Tue, 18 Mar 2025 17:51:34 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCT6P6Sz11cm; Tue, 18 Mar 2025 17:51:33 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 5/7] tests: Use lower-oci-image-state in container tests.
Date: Tue, 18 Mar 2025 18:51:10 +0100
Message-ID: <5e11bb8d29e14d75e1b09166efdda8932bb95e7b.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d1ebfa1736c..5dac8e80f22 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:08 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:08 2025
Received: from localhost ([127.0.0.1]:43079 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub6m-0007hR-64
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:08 -0400
Received: from confino.investici.org ([2a11:7980:1::2:0]:56063)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6F-0007cc-C9
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:35 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320293;
 bh=qC8u7mne2ffbrfQs41Rdd3Yp1wFxxukOCgah0j0nmRM=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=U+cUuxuaOOW6Ry5OlvO+xaVxsVwbawoBD0dIsRxO7UR12+vtKeARPne3hsgsUp74K
 wfynZl78Bb3dF5ms1SFKvFX3Wv0QUlLMDe2aQAp1UunkkC3FLMQdvdSZJcQcULnKIv
 phyyE2J/I6miDUtnj6NLVFnnW/JxkKNLD0wYUltw=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCT4qZxz11dd;
 Tue, 18 Mar 2025 17:51:33 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCT33jbz11cm; Tue, 18 Mar 2025 17:51:33 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 4/7] services: Add oci-service-type.
Date: Tue, 18 Mar 2025 18:51:09 +0100
Message-ID: <20663568040d1345979b6166caabcb4be1b28d10.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 +++++++--
 gnu/services/containers.scm | 1300 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 ++++++++++++++++++++++++++-
 4 files changed, 2390 insertions(+), 252 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 04885593328..521ea28dd5a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -42914,59 +42914,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources, it can be either a package or a string representing
+an absolute path to the runtime binary entrypoint.  When unset it will default
+to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
+package for the @code{'podman} runtime.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -42986,16 +43089,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -43003,22 +43106,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -43046,7 +43151,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -43057,10 +43162,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -43071,25 +43177,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 24f31c756b8..d1ebfa1736c 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,78 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -296,9 +367,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (not (maybe-value-set? maybe-group))
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -347,6 +451,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -374,10 +483,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -392,7 +506,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -437,11 +563,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -451,9 +581,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -477,15 +607,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -514,7 +645,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -526,9 +657,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -541,71 +673,348 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invocation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define-maybe/no-serialization package-or-string)
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package-or-string)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources, it can be either a package or a string representing
+an absolute path to the runtime binary entrypoint.  When unset it will default
+to @code{docker-cli} package for the @code{'docker} runtime or to @code{podman}
+package for the @code{'podman} runtime.")
+  (runtime-extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (not (maybe-value-set? runtime-cli))
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invocation)
+  "Return a Shepherd action printing a given INVOCATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invocation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invocation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -614,24 +1023,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -642,14 +1038,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -662,7 +1055,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -677,113 +1071,669 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invocation runtime runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invocation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run" "--rm"
+    ,@(if (eq? runtime 'podman)
+          ;; This is because podman takes some time to
+          ;; release container names.  --replace seems
+          ;; to be required to be able to restart services.
+          '("--replace")
+          '())
+    "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invocation #:key (verbose? #f) (pre-script #~()))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%")
+         (format #t "Current user: ~a ~a~%"
+                 (getuid) (passwd:name (getpwuid (getuid))))
+         (format #t "Current group: ~a ~a~%"
+                 (getgid) (group:name (getgrgid (getgid))))
+         (format #t "Current directory ~a~%" (getcwd)))
+       (define invocation (list #$@invocation))
+       #$@pre-script
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invocation))
+       (apply execlp `(,(first invocation) ,@invocation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invocation
+          (oci-container-run-invocation
+           runtime runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments))
+         (container-action
+          (lambda* (command #:key (environment-variables #f))
+            #~(lambda _
+                (fork+exec-command
+                 (list #$@command)
+                 #$@(if user (list #:user user) '())
+                 #$@(if group (list #:group group) '())
+                 #$@(if (maybe-value-set? log-file)
+                        (list #:log-file log-file)
+                        '())
+                 #$@(if (and user (eq? runtime 'podman))
+                        (list #:directory
+                              #~(passwd:dir (getpwnam #$user)))
+                        '())
+                 #$@(if environment-variables
+                        (list #:environment-variables
+                              environment-variables)
+                        '()))))))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
+                       (container-action
+                        (list (oci-container-entrypoint
+                               runtime runtime-cli name image image-reference
+                               invocation #:verbose? verbose?
+                               #:pre-script
+                               (if (oci-image? image)
+                                   #~((system*
+                                       #$(oci-image-loader
+                                          runtime runtime-cli name image
+                                          image-reference #:verbose? verbose?)))
+                                   #~())))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                       (container-action
+                        (list
+                         (oci-container-entrypoint
+                          runtime runtime-cli name image image-reference
+                          (list runtime-cli "rm" "-f" name)
+                          #:verbose? verbose?))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invocation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 (container-action
+                                  (list
+                                   (oci-container-entrypoint
+                                    runtime runtime-cli service-name image
+                                    image-reference
+                                    (list runtime-cli "pull" image)
+                                    #:verbose? verbose?))
+                                  #:environment-variables
+                                  #~(append
+                                     (list #$@host-environment)
+                                     (list #$@runtime-environment))))))))
+                        actions)))))
+
+(define (oci-object-create-invocation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invocation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invocations invocations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOCATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invocations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invocations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invocation)
+          (define name (last invocation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invocation))
+                (let ((exit-code (status:exit-val (apply system* invocation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invocations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invocations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOCATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invocations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invocations invocations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (network)
+            (oci-object-create-invocation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (volume)
+            (oci-object-create-invocation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  "Returns a list of Shepherd services based on the input OCI state."
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group
+          (if (eq? runtime 'podman)
+              #~(group:name
+                 (getgrgid
+                  (passwd:gid
+                   (getpwnam #$user))))
+              (oci-runtime-group config (oci-configuration-group config))))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                  #:user user
+                                  #:group group
+                                  #:verbose? verbose?
+                                  #:runtime-extra-arguments
+                                  runtime-extra-arguments
+                                  #:runtime-environment
+                                  (oci-runtime-system-environment runtime user)
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime)
+                                  #:networks-requirement '(networking))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (not (maybe-value-set? subgids))
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (not (maybe-value-set? subuids))
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((maybe-value-set? runtime-cli)
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 828ceea313a..125e748bb0e 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -297,17 +302,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:52:08 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:52:08 2025
Received: from localhost ([127.0.0.1]:43077 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub6l-0007hP-C5
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:52:08 -0400
Received: from confino.investici.org ([93.190.126.19]:59583)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub6E-0007cW-1n
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:51:34 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320293;
 bh=HzRTYHO2F4KMK9qeHra0pSyBzvuUEie4gJwFMFBOzbo=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=kcz+YOX2B/ixrnute8XqlKeRrVXkZR/AyQyuPN2JcGasCJOH+2lCV3QTzM2XgDJvD
 lZiTt0B+YNZ3oftrPPv6mvnL22csYLpvKH8hq03iDnCuXNuBKJv4UnxiX7WuViyS59
 jMaUHvzmtHNpccvkY5Xd7u2Cm6ORmTw/homp3t0Y=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKCT1567z11d4;
 Tue, 18 Mar 2025 17:51:33 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKCQ2n0Cz11cm; Tue, 18 Mar 2025 17:51:30 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v9 3/7] tests: oci-container: Set explicit timeouts.
Date: Tue, 18 Mar 2025 18:51:08 +0100
Message-ID: <f4307abc36664f1d8ba3ddcd79709f6164c23860.1742320272.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
References: <15af7d63ead9dfad272154c09bdfda718da1047c.1742320272.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

* gnu/tests/docker.scm: Simplify %test-oci-container test case and add
explicit timeouts to tests outcomes.
---
 gnu/tests/docker.scm | 99 ++++++++++++++++++--------------------------
 1 file changed, 41 insertions(+), 58 deletions(-)

diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Mar 2025 17:50:59 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 18 13:50:59 2025
Received: from localhost ([127.0.0.1]:43067 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tub5f-0007Xl-Ap
	for submit <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:50:59 -0400
Received: from confino.investici.org ([93.190.126.19]:20399)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tub5b-0007X2-1S
 for 76081 <at> debbugs.gnu.org; Tue, 18 Mar 2025 13:50:57 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1742320253;
 bh=X/JkmbaEmBYfUmsgN95hA9CQm1ouYKVgwEOH67dxITI=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=TC0z5LkdEoBLdF65by052/dLwQsh8vbCRSdZOQSEr/hhlDqFpPz3UGe1Zqgp5Gmc3
 MHCTlFziuHCeDe50+KL++RYKgEH9cpufvbbnp0x8AD8CI5zXlc/9n9lQp9481tMfHv
 6qmpC5gX78s6hDmrGlwH/4XvC4jx7Yf5U+fG2Z/U=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4ZHKBj0VH3z11d3
 for <76081 <at> debbugs.gnu.org>; Tue, 18 Mar 2025 17:50:53 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4ZHKBh75Bkz11cm
 for <76081 <at> debbugs.gnu.org>; Tue, 18 Mar 2025 17:50:52 +0000 (UTC)
Message-ID: <020bb939-819c-4de3-bc04-ef0f33855c90@HIDDEN>
Date: Tue, 18 Mar 2025 18:50:14 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
 <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
 <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
 <a559945e-925c-46f5-a591-19154712b269@HIDDEN>
 <afa88170-8a5e-42a3-b024-caeef44da51f@HIDDEN>
Content-Language: en-US
In-Reply-To: <afa88170-8a5e-42a3-b024-caeef44da51f@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

Hi,

On 3/9/25 01:27, paul wrote:
> Hi,
>
> On 3/4/25 13:39, paul wrote:
>> Hi,
>>
>> On 2/27/25 00:01, paul wrote:
>>> Hi guix,
>>>
>>> On 2/18/25 02:20, paul wrote:
>>>> Hi,
>>>>
>>>> On 2/12/25 02:09, paul wrote:
>>>>> Hi guix,
>>>>>
>>>>> On 2/9/25 21:38, paul wrote:
>>>>>> I'm sending a v3 fixing a bug in the merge algorithm for volumes 
>>>>>> and networks.
>>>>>>
>>>>>> On 2/9/25 20:14, paul wrote:
>>>>>>> Hi,
>>>>>>>
>>>>>>> I'm about to send a v2. v2 compared to the first revision features:
>>>>>>>
>>>>>>>
>>>>>>> - it actually compiles all the times :) (rev 1 referenced 
>>>>>>> oci-image too early for it to be working and generated a compile 
>>>>>>> time error, if you recompiled it sometimes went away so I 
>>>>>>> thought it was a problem of my setup. CI caught this)
>>>>>>> - it allows more values to be overridden by eventual users of 
>>>>>>> the Scheme API
>>>>>>> - it allows passing extra arguments directly after each podman 
>>>>>>> or docker invokation, allowing for example for overriding podman 
>>>>>>> --root and similar options.
>>>>>>>
>>>>>>> All of these tests should pass:
>>>>>>>
>>>>>>> guix shell -D guix -CPW -- make check-system 
>>>>>>> TESTS="oci-container oci-service-rootless-podman docker 
>>>>>>> docker-system rootless-podman oci-service-docker"
>>>>>>>
>>>>> I'm sending a v4 changing slightly the image loader, the same 
>>>>> tests as before are supposed to pass. Now the Home service [0] is 
>>>>> working for me with rootless podman. I'll try it on different 
>>>>> distros if I manage to.
>>>>
>>>> I'm sending a v5 implementing a Home service. The changes compared 
>>>> to v4 are pretty trivial as the plumbing was already there, the 
>>>> only downside is that I'm not able to use for-home? in 
>>>> define-configuration, so I had to reimplement oci-configuration 
>>>> with (guix records) and had to reimplement some validation (gnu 
>>>> services configuration) would figure out magically.
>>>
>>> I'm sending  a v6. Compared to v5 it resolves the conflicts with 
>>> master and it should fix the stop action for rootless podman backed 
>>> services.
>>
>> I'm sending a v7 fixing the restart action for podman backed services.
>
> I'm sending a v8 fixing some further bugs wrt to the start and stop of 
> podman backed services.

In the hope of making the review easier, I'm sending a v9 that is 
completely the same of v8 in terms of changes but with smaller commits. 
Hopefully this makes it easier to review changes, please let me know if 
I can do anything to ease the workload for reviewers of this patch set.

Thank you for your work,

cheers

giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 01:06:41 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 20:06:41 2025
Received: from localhost ([127.0.0.1]:57388 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr57n-0000Jp-6E
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:41 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:50417)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr57Z-0000IZ-Cd
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:29 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741482383;
 bh=5w1pguUy5euk/p6I7KBfaqhA/lWVRKQOsLxduTGYZqY=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=n6AxZ2HVYVPW9xmWASiPyYFkZMXDVQPI+Z6E0N4Mj2UpzghD1hWHVAxjibPFf2kWC
 18KAR1PFcP+yb4LCJcfH/uSshfifcqv1LQGSVzvFnLumUFAICn2moaRqI3HFNjxyY0
 P/crJdliXiMElEevKxI6Y5b2Tof4QvDmZgN6TXmg=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9MKq0TGnz11NQ;
 Sun,  9 Mar 2025 01:06:23 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9MKp6Ctmz11NJ; Sun,  9 Mar 2025 01:06:22 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v8 2/5] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Sun,  9 Mar 2025 02:06:12 +0100
Message-ID: <5ea9ef46b656b23c6213ec086084fd572b10816a.1741482375.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
References: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -190,7 +239,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -309,495 +297,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invocation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 01:06:31 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 20:06:31 2025
Received: from localhost ([127.0.0.1]:57386 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr57e-0000JR-5a
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:31 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:43993)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr57Z-0000Id-CW
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:27 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741482384;
 bh=M48IKbYcoh+yVWFUUliCPB3CIz3BAUfXQ15AWJnNPUU=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=dk1hva4SBhqkpFjss9zXPo6IL+cR7j02CW/zJ4rbx6VG12f7feKHdiPAQfldGgaYu
 MdBTUiOpmrBneNM5I80sOVjpghVhJMwfBc4jwUvDKQL8qKQ51xfOlcLXlqQbPh4ztL
 I+bxvGVd5fuH3TcBiaIkHkZRIj9HtYje1bPCtFl8=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9MKr1jvNz11Nd;
 Sun,  9 Mar 2025 01:06:24 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9MKr0jRNz11NJ; Sun,  9 Mar 2025 01:06:24 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v8 5/5] home: Add home-oci-service-type.
Date: Sun,  9 Mar 2025 02:06:15 +0100
Message-ID: <dc0883c8d73e54a7f1e505dbee707f90a7de50c7.1741482375.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
References: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 gnu/services/containers.scm      |   5 ++
 4 files changed, 170 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 8686380669b..7ed469f7920 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -50403,6 +50403,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index 9082ed04bfe..e0d1a25a607 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -103,6 +103,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 700c7b63603..002bbc1057b 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -762,6 +762,9 @@ (define-configuration/no-serialization oci-network-configuration
 (define (list-of-oci-networks? value)
   (list-of-oci-records? "networks" oci-network-configuration? value))
 
+;; (for-home (oci-configuration ...)) is not able to replace for-home? with #t,
+;; pk prints #f. Once for-home will be able to work with (gnu services configuration) the
+;; record can be migrated back to define-configuration.
 (define-record-type* <oci-configuration>
   oci-configuration
   make-oci-configuration
@@ -796,6 +799,8 @@ (define-record-type* <oci-configuration>
 (define (package-or-string? value)
   (or (package? value) (string? value)))
 
+;; TODO: This procedure can be dropped once we switch to define-configuration for
+;; oci-configuration.
 (define (oci-configuration-valid? config)
   (define runtime-cli
     (oci-configuration-runtime-cli config))
-- 
2.48.1





Information forwarded to andrew@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 01:06:30 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 20:06:30 2025
Received: from localhost ([127.0.0.1]:57384 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr57d-0000JE-4f
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:30 -0500
Received: from confino.investici.org ([93.190.126.19]:22917)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr57Z-0000Ib-9l
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:27 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741482383;
 bh=OA+jF6nMN1H/fbeqERhZSvlXDfNZ/EM7zWgOCJPe3Mg=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=d3e6ZneUAN+y+ZnyVH9sadsoxifWXk9lS1voExqV8mWbOZ4iu1cs5wxbzjeoZSR94
 +TZExxFg62UUuoD1KBeRJxUsLML/ShW2VPhR0T6BqhgqgQoDkPh4fbSmWlteXLHaR+
 0z9aHPFqsYazk9n4cP7wOM8QZgvQWFgSJLIG5IsE=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9MKq66Ljz11NV;
 Sun,  9 Mar 2025 01:06:23 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9MKq57M0z11NJ; Sun,  9 Mar 2025 01:06:23 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v8 4/5] tests: Use lower-oci-image-state in container tests.
Date: Sun,  9 Mar 2025 02:06:14 +0100
Message-ID: <2801dce6518c3f6e99d76ec7010c8108114c224d.1741482375.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
References: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index a78be00f038..700c7b63603 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -75,6 +75,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 01:06:29 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 20:06:29 2025
Received: from localhost ([127.0.0.1]:57382 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr57c-0000J9-O1
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:29 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:32619)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr57Z-0000IY-AV
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:26 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741482382;
 bh=AJ4/OJL3IyQG00I5i1Z/KH7n+uwTOfq3PFWqIhSVXsw=;
 h=From:To:Cc:Subject:Date:From;
 b=J0v3+JL7rRNTDwEmPNYqmhINDY7Zw19ydBYBALNr49hB/0S53okIhiZKEQbfYS5m0
 Y0HWMTxesXDY5RCgbuuMk25Byozsbz0ZapKOMWY0WSJl97YPitdV0ufZTK5NEZ9pW6
 VoyPZv4hPA55wGHOR3Dq6HT42cvg7lWVhWOdgT4Q=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9MKp4jpqz11NM;
 Sun,  9 Mar 2025 01:06:22 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9MKp3Q1mz11NJ; Sun,  9 Mar 2025 01:06:22 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v8 1/5] services: rootless-podman: Use login shell.
Date: Sun,  9 Mar 2025 02:06:11 +0100
Message-ID: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index b3cd109ce6c..d5a211765a6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 5adfe1b8e92ff332656bcc7a9d71a35306b3411e
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 01:06:28 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 20:06:28 2025
Received: from localhost ([127.0.0.1]:57381 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr57c-0000J3-8A
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:28 -0500
Received: from confino.investici.org ([93.190.126.19]:44599)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr57Z-0000Ia-7e
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 20:06:25 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741482383;
 bh=OFurPoE5KAFCORjZk5Ct+DmNJsmN2g+9Hhckv+da+i4=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=cAoPAeP2FWOMtdeHrpZdxmoCUfAjZ1oaJ8heXML3dv9Wmsuc644kd0xtSB1EP9PFu
 +oMh1f89EUKc0MIZX117VKtxXIvJmH7PO13lYWQmOL+m8IDGldnoJto599bXUlVSUT
 4M3eqiV556HYV8edMoTaLTCB24wH84XpjzGUYdog=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9MKq3f8sz11NT;
 Sun,  9 Mar 2025 01:06:23 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9MKq2LHKz11NJ; Sun,  9 Mar 2025 01:06:23 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v8 3/5] services: Add oci-service-type.
Date: Sun,  9 Mar 2025 02:06:13 +0100
Message-ID: <c2943e4b72e89e50daadf0a9e30562096bbe3b77.1741482375.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
References: <36e9d9e4474b8d547a86a0225e89f0039af39970.1741482375.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 ++++++--
 gnu/services/containers.scm | 1335 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++-
 4 files changed, 2425 insertions(+), 252 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 7d8a5243ed8..8686380669b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -42839,59 +42839,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.  When a string is passed it will be interpreted as the
+absolute file-system path of the selected OCI runtime command.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -42911,16 +43014,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -42928,22 +43031,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -42971,7 +43076,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -42982,10 +43087,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -42996,25 +43102,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 24f31c756b8..a78be00f038 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,8 +39,10 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +98,80 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-extension-wrap-validate
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -296,9 +370,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (eq? maybe-group #f)
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -347,6 +454,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -374,10 +486,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -392,7 +509,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -437,11 +566,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -451,9 +584,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -477,15 +610,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -514,7 +648,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -526,9 +660,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -541,71 +676,363 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invocation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (eq? runtime-cli #f)
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invocation)
+  "Return a Shepherd action printing a given INVOCATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invocation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invocation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -614,24 +1041,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -642,14 +1056,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -662,7 +1073,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -677,113 +1089,686 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invocation runtime runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invocation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run" "--rm"
+    ,@(if (eq? runtime 'podman)
+          ;; This is because podman takes some time to
+          ;; release container names.  --replace seems
+          ;; to be required to be able to restart services.
+          '("--replace")
+          '())
+    "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invocation #:key (verbose? #f) (pre-script #~()))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%")
+         (format #t "Current user: ~a ~a~%"
+                 (getuid) (passwd:name (getpwuid (getuid))))
+         (format #t "Current group: ~a ~a~%"
+                 (getgid) (group:name (getgrgid (getgid))))
+         (format #t "Current directory ~a~%" (getcwd)))
+       (define invocation (list #$@invocation))
+       #$@pre-script
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invocation))
+       (apply execlp `(,(first invocation) ,@invocation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invocation
+          (oci-container-run-invocation
+           runtime runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments))
+         (container-action
+          (lambda* (command #:key (environment-variables #f))
+            #~(lambda _
+                (fork+exec-command
+                 (list #$@command)
+                 #$@(if user (list #:user user) '())
+                 #$@(if group (list #:group group) '())
+                 #$@(if (maybe-value-set? log-file)
+                        (list #:log-file log-file)
+                        '())
+                 #$@(if (and user (eq? runtime 'podman))
+                        (list #:directory
+                              #~(passwd:dir (getpwnam #$user)))
+                        '())
+                 #$@(if environment-variables
+                        (list #:environment-variables
+                              environment-variables)
+                        '()))))))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
+                       (container-action
+                        (list (oci-container-entrypoint
+                               runtime runtime-cli name image image-reference
+                               invocation #:verbose? verbose?
+                               #:pre-script
+                               (if (oci-image? image)
+                                   #~((system*
+                                       #$(oci-image-loader
+                                          runtime runtime-cli name image
+                                          image-reference #:verbose? verbose?)))
+                                   #~())))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                       (container-action
+                        (list
+                         (oci-container-entrypoint
+                          runtime runtime-cli name image image-reference
+                          (list runtime-cli "rm" "-f" name)
+                          #:verbose? verbose?))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invocation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 (container-action
+                                  (list
+                                   (oci-container-entrypoint
+                                    runtime runtime-cli service-name image
+                                    image-reference
+                                    (list runtime-cli "pull" image)
+                                    #:verbose? verbose?))
+                                  #:environment-variables
+                                  #~(append
+                                     (list #$@host-environment)
+                                     (list #$@runtime-environment))))))))
+                        actions)))))
+
+(define (oci-object-create-invocation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invocation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invocations invocations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOCATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invocations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invocations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invocation)
+          (define name (last invocation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invocation))
+                (let ((exit-code (status:exit-val (apply system* invocation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invocations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invocations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOCATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invocations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invocations invocations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (network)
+            (oci-object-create-invocation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (volume)
+            (oci-object-create-invocation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  "Returns a list of Shepherd services based on the input OCI state."
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group
+                                      (oci-runtime-system-group runtime user group)
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subgids #f)
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subuids #f)
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((not (eq? runtime-cli #f))
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension subids-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
+                  (service-extension account-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
+                  (service-extension shepherd-root-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 828ceea313a..125e748bb0e 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -297,17 +302,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Mar 2025 00:27:56 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Mar 08 19:27:56 2025
Received: from localhost ([127.0.0.1]:57338 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tr4WJ-0006xP-O6
	for submit <at> debbugs.gnu.org; Sat, 08 Mar 2025 19:27:56 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:24593)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tr4WH-0006xE-F7
 for 76081 <at> debbugs.gnu.org; Sat, 08 Mar 2025 19:27:54 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741480069;
 bh=tKyjtrc8P3uG9BnqjgJ+oVvgsvACORwpQCsbdPOAG+I=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=C0hHTL22c//Ew4egn270cllIJfPxVNoMdk8l1yufXfS7j0WLprZ4LNLPuVnLRjLr5
 TetZbuEvaq4ES/neGi26Oq6Ix0bflMC0VTtPwSqYJLQD0NcEYrL1qXwVfal1Wz/ElQ
 iIZLhMzkNN6E6IEJIkKXP5fCTrVRVaRWK2rC9JV4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z9LTK0ChQz11NM
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Mar 2025 00:27:49 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z9LTJ6qSsz11NJ
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Mar 2025 00:27:48 +0000 (UTC)
Message-ID: <afa88170-8a5e-42a3-b024-caeef44da51f@HIDDEN>
Date: Sun, 9 Mar 2025 01:27:48 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
 <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
 <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
 <a559945e-925c-46f5-a591-19154712b269@HIDDEN>
Content-Language: en-US
In-Reply-To: <a559945e-925c-46f5-a591-19154712b269@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

On 3/4/25 13:39, paul wrote:
> Hi,
>
> On 2/27/25 00:01, paul wrote:
>> Hi guix,
>>
>> On 2/18/25 02:20, paul wrote:
>>> Hi,
>>>
>>> On 2/12/25 02:09, paul wrote:
>>>> Hi guix,
>>>>
>>>> On 2/9/25 21:38, paul wrote:
>>>>> I'm sending a v3 fixing a bug in the merge algorithm for volumes 
>>>>> and networks.
>>>>>
>>>>> On 2/9/25 20:14, paul wrote:
>>>>>> Hi,
>>>>>>
>>>>>> I'm about to send a v2. v2 compared to the first revision features:
>>>>>>
>>>>>>
>>>>>> - it actually compiles all the times :) (rev 1 referenced 
>>>>>> oci-image too early for it to be working and generated a compile 
>>>>>> time error, if you recompiled it sometimes went away so I thought 
>>>>>> it was a problem of my setup. CI caught this)
>>>>>> - it allows more values to be overridden by eventual users of the 
>>>>>> Scheme API
>>>>>> - it allows passing extra arguments directly after each podman or 
>>>>>> docker invokation, allowing for example for overriding podman 
>>>>>> --root and similar options.
>>>>>>
>>>>>> All of these tests should pass:
>>>>>>
>>>>>> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
>>>>>> oci-service-rootless-podman docker docker-system rootless-podman 
>>>>>> oci-service-docker"
>>>>>>
>>>> I'm sending a v4 changing slightly the image loader, the same tests 
>>>> as before are supposed to pass. Now the Home service [0] is working 
>>>> for me with rootless podman. I'll try it on different distros if I 
>>>> manage to.
>>>
>>> I'm sending a v5 implementing a Home service. The changes compared 
>>> to v4 are pretty trivial as the plumbing was already there, the only 
>>> downside is that I'm not able to use for-home? in 
>>> define-configuration, so I had to reimplement oci-configuration with 
>>> (guix records) and had to reimplement some validation (gnu services 
>>> configuration) would figure out magically.
>>
>> I'm sending  a v6. Compared to v5 it resolves the conflicts with 
>> master and it should fix the stop action for rootless podman backed 
>> services.
>
> I'm sending a v7 fixing the restart action for podman backed services.

I'm sending a v8 fixing some further bugs wrt to the start and stop of 
podman backed services.

Thank you for your work,

cheers

giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:41:15 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:41:15 2025
Received: from localhost ([127.0.0.1]:56159 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRaC-0000eU-LQ
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:15 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:27941)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRa6-0000dL-KV
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:10 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741092063;
 bh=5w1pguUy5euk/p6I7KBfaqhA/lWVRKQOsLxduTGYZqY=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=VwtpKCI5x+4OcZFMSMpn9Ooslp6UkkXKlOAgxW53VKcX0HJwna4zVRtRtcsnMnxkE
 zr9hkTdbDtVVxdsge+I3a/7XlDvboqyY8HiM1OQT6N+keeWBupq++uqE+RW6K72T/R
 jbxLfVEUBajdUPWZv7y5cqit8eS9BVX+Ih92tblY=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zzg5wrhz115H;
 Tue,  4 Mar 2025 12:41:03 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zzg459jz1122; Tue,  4 Mar 2025 12:41:03 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v7 2/5] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Tue,  4 Mar 2025 13:39:59 +0100
Message-ID: <e6437536b0e550b1a4cfdff08ddb114b9d016609.1741092002.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
References: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -190,7 +239,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -309,495 +297,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invocation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:41:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:41:12 2025
Received: from localhost ([127.0.0.1]:56157 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRaC-0000eM-0C
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:12 -0500
Received: from confino.investici.org ([93.190.126.19]:33711)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRa6-0000dR-FD
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:07 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741092065;
 bh=h0plgAp9LHO8TolUragh/3iEzRwLganm4zDBr+kUGz4=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=tWTYCpkv8D699W8OJsN7AKc+NIh290rfC9R+QVDZvHvB7H5QzR/1uRGEIf2lOT1MF
 I2A8woylV87MmBraVNPl243segMdERWarWSDyv3jNxz1gU0cRNjvkU688oV0WflBjG
 GUesc6STfYTV3qYQiCrwVnEbr9xyNC/6HniefUT4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zzj40g9z115b;
 Tue,  4 Mar 2025 12:41:05 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zzj1HKKz1122; Tue,  4 Mar 2025 12:41:05 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v7 5/5] home: Add home-oci-service-type.
Date: Tue,  4 Mar 2025 13:40:02 +0100
Message-ID: <cacc5fec364e79bba8074bd40792ee9ce14cc089.1741092002.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
References: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 3 files changed, 165 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index be5c8cf8fad..6c09f929049 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -50288,6 +50288,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index a7a3238669d..d48e767154c 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -103,6 +103,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
-- 
2.48.1





Information forwarded to andrew@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:41:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:41:12 2025
Received: from localhost ([127.0.0.1]:56155 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRaB-0000eF-L9
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:11 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:61221)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRa6-0000dJ-4i
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:06 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741092063;
 bh=Cu4zAqdvKiPIsz0kq/O65HDxVqxYKw8mLcYnRakqTms=;
 h=From:To:Cc:Subject:Date:From;
 b=rOPeoVGf2SyzrRCNUeWahaQrqEJYLk0oJrykswyAn0Sei88WvRdherSIg1fVz9uZu
 C3/o4lR1xR7Cut+7ta1CK6SoCMc1AkMaVyK7kWueZyjNwugdg4h+5478P6+nls+qxp
 hyZVHrFYQ7x9HVuCDEqXycIfErkY4ifviTU4HYjs=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zzg2N23z115G;
 Tue,  4 Mar 2025 12:41:03 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zzg112hz1122; Tue,  4 Mar 2025 12:41:03 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v7 1/5] services: rootless-podman: Use login shell.
Date: Tue,  4 Mar 2025 13:39:58 +0100
Message-ID: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index b3cd109ce6c..d5a211765a6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 72923a75af53a819f2be9dc4ae3c096aa3147d3f
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:41:11 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:41:11 2025
Received: from localhost ([127.0.0.1]:56153 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRaA-0000eA-MU
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:11 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:55513)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRa5-0000dO-NR
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:06 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741092064;
 bh=NX55x0vzZ7+snJ/vUqL2Po2HeLZqsb5E7PUjP7o0CRQ=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=mvCFPPzGw7UrcSJYgAauKF5DrphIgzQLkQBiafEiZSCtsuA1KEeXJq/Yi5YdM1Hz1
 vEauzncxqgsKdLl52W0XHkre936R+EJEVJZLR2uRS4J9zf/Z7wfFSKnIwOljWD2WUu
 9wMYnC65nMzdZ9ZlT5ZwrHMyD+Tsl2zQrUw5onVg=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zzh6VMvz115T;
 Tue,  4 Mar 2025 12:41:04 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zzh5C2Nz1122; Tue,  4 Mar 2025 12:41:04 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v7 4/5] tests: Use lower-oci-image-state in container tests.
Date: Tue,  4 Mar 2025 13:40:01 +0100
Message-ID: <eecc4358d0e2a0e37fb37789c812183923cd263f.1741092002.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
References: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 4600846ac3d..57b14868f1a 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -75,6 +75,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:41:08 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:41:08 2025
Received: from localhost ([127.0.0.1]:56152 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRa8-0000ds-GY
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:08 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:35699)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRa5-0000dM-MD
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:41:06 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741092064;
 bh=M+PjDOaJyxGTs8vBXtUuaOIziKnrWlv2zL4zzHQlBzQ=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=XtEwGVjOg+Art40rQ5ihhBVn4Dxqrvw4miAxXweWr6Qwk7uandWYKgvUGe1VSMADY
 yCJNhvUjb+Bub46WcOVMqqHtXSsKMCnmwKIlnJyeJFb54/KA4lrymReRUpCTrNO9Pz
 X/0m9xKftIJWkVfLB5JFTUNb1XAItpRrgPd85M+U=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zzh3X3hz115V;
 Tue,  4 Mar 2025 12:41:04 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zzh11Nwz1122; Tue,  4 Mar 2025 12:41:04 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v7 3/5] services: Add oci-service-type.
Date: Tue,  4 Mar 2025 13:40:00 +0100
Message-ID: <88bcb0d883db7ad2b1c1a997a3a4e852ef386ea1.1741092002.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
References: <ec7962db7e8300a129f7836ce44733dffb435a1c.1741092002.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 ++++++--
 gnu/services/containers.scm | 1320 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++-
 4 files changed, 2410 insertions(+), 252 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 551bc52f7f6..be5c8cf8fad 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -42727,59 +42727,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.  When a string is passed it will be interpreted as the
+absolute file-system path of the selected OCI runtime command.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -42799,16 +42902,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -42816,22 +42919,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -42859,7 +42964,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -42870,10 +42975,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -42884,25 +42990,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 24f31c756b8..4600846ac3d 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,8 +39,10 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +98,80 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-extension-wrap-validate
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -296,9 +370,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (eq? maybe-group #f)
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -347,6 +454,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -374,10 +486,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -392,7 +509,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -437,11 +566,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -451,9 +584,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -477,15 +610,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -514,7 +648,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -526,9 +660,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -541,71 +676,363 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invocation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (eq? runtime-cli #f)
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invocation)
+  "Return a Shepherd action printing a given INVOCATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invocation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invocation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -614,24 +1041,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -642,14 +1056,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -662,7 +1073,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -677,113 +1089,671 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invocation runtime runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invocation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run" "--rm"
+    ,@(if (eq? runtime 'podman)
+          ;; This is because podman takes some time to
+          ;; release container names.  --replace seems
+          ;; to be required to be able to restart services.
+          '("--replace")
+          '())
+    "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invocation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invocation (list #$@invocation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invocation))
+       (apply execlp `(,(first invocation) ,@invocation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invocation
+          (oci-container-run-invocation
+           runtime runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments))
+         (wrap-command
+          (lambda (command)
+            (if (eq? runtime 'podman)
+                (list
+                 "/bin/sh" "-l" "-c"
+                 #~(string-join (list #$@command) " "))
+                command)))
+         (container-action
+          (lambda* (command #:key (environment-variables #f))
+            #~(lambda _
+                (fork+exec-command
+                 (list #$@command)
+                 #$@(if user (list #:user user) '())
+                 #$@(if group (list #:group group) '())
+                 #$@(if (maybe-value-set? log-file)
+                        (list #:log-file log-file)
+                        '())
+                 #$@(if (and user (eq? runtime 'podman))
+                        (list #:directory
+                              #~(passwd:dir (getpwnam #$user)))
+                        '())
+                 #$@(if environment-variables
+                        (list #:environment-variables
+                              environment-variables)
+                        '()))))))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
+                       (container-action
+                        (list (oci-container-entrypoint
+                               runtime runtime-cli name image image-reference
+                               invocation #:verbose? verbose?))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                       (container-action
+                        (wrap-command
+                         (list runtime-cli "rm" "-f" name))))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invocation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 (container-action
+                                  (wrap-command
+                                   (list runtime-cli "pull" image))))))))
+                        actions)))))
+
+(define (oci-object-create-invocation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invocation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invocations invocations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOCATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invocations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invocations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invocation)
+          (define name (last invocation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invocation))
+                (let ((exit-code (status:exit-val (apply system* invocation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invocations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invocations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOCATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invocations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invocations invocations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (network)
+            (oci-object-create-invocation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invocations
+         (map
+          (lambda (volume)
+            (oci-object-create-invocation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invocations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  "Returns a list of Shepherd services based on the input OCI state."
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group
+                                      (oci-runtime-system-group runtime user group)
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subgids #f)
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subuids #f)
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((not (eq? runtime-cli #f))
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension subids-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
+                  (service-extension account-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
+                  (service-extension shepherd-root-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 828ceea313a..125e748bb0e 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -297,17 +302,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 4 Mar 2025 12:39:42 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Mar 04 07:39:42 2025
Received: from localhost ([127.0.0.1]:56138 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tpRYj-0000U1-IO
	for submit <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:39:42 -0500
Received: from confino.investici.org ([93.190.126.19]:46275)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tpRYe-0000Tm-EZ
 for 76081 <at> debbugs.gnu.org; Tue, 04 Mar 2025 07:39:39 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1741091974;
 bh=8DGpHwqkpANSw5LzIMEQSItgGLcbHrNg6LQB0uL6wYo=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=tcUWfKgZcZoAe3oHuQYXoJyX+fwd+kOmv8UInKp67ke3NmLKgDlPyZ74W0HGtY9vq
 /ZbUH3zj82X4o5Rz3E6bYd0J1UETyIwxBMFKwZXfXrTFeyA1AB3pca6aTbQaywckoq
 cr+WNc4FOjfcuHlStWTaZO6+tBjy7aVDyO6+it1g=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z6Zxy2R0wz1122
 for <76081 <at> debbugs.gnu.org>; Tue,  4 Mar 2025 12:39:34 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z6Zxy1jc0z111F
 for <76081 <at> debbugs.gnu.org>; Tue,  4 Mar 2025 12:39:34 +0000 (UTC)
Message-ID: <a559945e-925c-46f5-a591-19154712b269@HIDDEN>
Date: Tue, 4 Mar 2025 13:39:33 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
 <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
 <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
Content-Language: en-US
In-Reply-To: <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

Hi,

On 2/27/25 00:01, paul wrote:
> Hi guix,
>
> On 2/18/25 02:20, paul wrote:
>> Hi,
>>
>> On 2/12/25 02:09, paul wrote:
>>> Hi guix,
>>>
>>> On 2/9/25 21:38, paul wrote:
>>>> I'm sending a v3 fixing a bug in the merge algorithm for volumes 
>>>> and networks.
>>>>
>>>> On 2/9/25 20:14, paul wrote:
>>>>> Hi,
>>>>>
>>>>> I'm about to send a v2. v2 compared to the first revision features:
>>>>>
>>>>>
>>>>> - it actually compiles all the times :) (rev 1 referenced 
>>>>> oci-image too early for it to be working and generated a compile 
>>>>> time error, if you recompiled it sometimes went away so I thought 
>>>>> it was a problem of my setup. CI caught this)
>>>>> - it allows more values to be overridden by eventual users of the 
>>>>> Scheme API
>>>>> - it allows passing extra arguments directly after each podman or 
>>>>> docker invokation, allowing for example for overriding podman 
>>>>> --root and similar options.
>>>>>
>>>>> All of these tests should pass:
>>>>>
>>>>> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
>>>>> oci-service-rootless-podman docker docker-system rootless-podman 
>>>>> oci-service-docker"
>>>>>
>>> I'm sending a v4 changing slightly the image loader, the same tests 
>>> as before are supposed to pass. Now the Home service [0] is working 
>>> for me with rootless podman. I'll try it on different distros if I 
>>> manage to.
>>
>> I'm sending a v5 implementing a Home service. The changes compared to 
>> v4 are pretty trivial as the plumbing was already there, the only 
>> downside is that I'm not able to use for-home? in 
>> define-configuration, so I had to reimplement oci-configuration with 
>> (guix records) and had to reimplement some validation (gnu services 
>> configuration) would figure out magically.
>
> I'm sending  a v6. Compared to v5 it resolves the conflicts with 
> master and it should fix the stop action for rootless podman backed 
> services.

I'm sending a v7 fixing the restart action for podman backed services.


Thank you for your work,


giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:08:53 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:08:53 2025
Received: from localhost ([127.0.0.1]:56079 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQWI-0001HJ-F6
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:52 -0500
Received: from confino.investici.org ([93.190.126.19]:55527)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQVr-0001FX-UE
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:28 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740611302;
 bh=5w1pguUy5euk/p6I7KBfaqhA/lWVRKQOsLxduTGYZqY=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=rfgDNHeGaHXBfm1bcE/EpMDUqbRreZm4v07akeYBirTBr8OyaOfbYyCtO/sVPkOCh
 EO1Fp/O7647B5Wk6LiebHsdtScspOGDkEUfTor8QUoOXac0EllRJBTOUDentAeQIlv
 In9YCudfPhqpc4KK6lNpbgQIcmjaTy4q99fBrxkI=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z39BG0ycMz117r;
 Wed, 26 Feb 2025 23:08:22 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39BF6q1Rz111L; Wed, 26 Feb 2025 23:08:21 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v6 2/5] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Thu, 27 Feb 2025 00:08:07 +0100
Message-ID: <4b79084a63c98ff9b15c925af93455ef1f1c1fd1.1740611290.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
References: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -190,7 +239,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -309,495 +297,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invocation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:08:29 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:08:29 2025
Received: from localhost ([127.0.0.1]:56077 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQVw-0001GL-7o
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:29 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:61379)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQVs-0001Fb-3j
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:26 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740611303;
 bh=kHL9LMwU8v0P9d3hNCgHuoQg6btk38l3Y+bbF98TWYo=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=BV9Y8/W9H8RbmZJSZPXk375M8NNlDmMP+0XYh26r6uB6B95Xj9WyFAzpRmXdQyqeA
 Lvw81SRmLpU6V8tSFWh5Zz4p+50+JJVihVPQEWSkgUHRZwn5I/iYWlbgLDZHv4xN1t
 JZcQZMEcL7qXlh9zULX4ObfpyFh3+KTlPXyxWHNQ=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z39BH2FtDz11Dc;
 Wed, 26 Feb 2025 23:08:23 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39BH1JlSz111L; Wed, 26 Feb 2025 23:08:23 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v6 5/5] home: Add home-oci-service-type.
Date: Thu, 27 Feb 2025 00:08:10 +0100
Message-ID: <bd9961d8d23b364ea1cc14cb1869f0dacfdc39a8.1740611290.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
References: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 3 files changed, 165 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index fd08261af3b..5513eb89048 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -49876,6 +49876,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index c8a29bf98b5..9b86e59c37f 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -102,6 +102,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
-- 
2.48.1





Information forwarded to andrew@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:08:28 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:08:28 2025
Received: from localhost ([127.0.0.1]:56075 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQVv-0001GD-6y
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:28 -0500
Received: from confino.investici.org ([93.190.126.19]:47071)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQVr-0001Fa-Ts
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:25 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740611302;
 bh=PpnlXDiHDweVa3+k4DAj5IYR0vx7Oa4zoSqOksxGVQ0=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=n7mUOHqnzjJ1gFqh0Vhu83n9q55obiyQrrDSso8SH0wOQyn8s8Ddpa2EEG9l/plgS
 ydiKfGYDGTMyAASgrFACBW9c/SOdvPrthtcpAC8i1/V2bYBSjpgHdoD+pwyBRMg7ga
 eOZoP9uljmBwFci45fb61x625zVgG5pzB3WqIb70=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z39BG6pnVz11D7;
 Wed, 26 Feb 2025 23:08:22 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39BG5ZYFz111L; Wed, 26 Feb 2025 23:08:22 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v6 4/5] tests: Use lower-oci-image-state in container tests.
Date: Thu, 27 Feb 2025 00:08:09 +0100
Message-ID: <7d3f6521d783c726f555d906881624eed5a523c2.1740611290.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
References: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 603eec25e2b..139d5e4ec50 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -75,6 +75,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:08:27 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:08:27 2025
Received: from localhost ([127.0.0.1]:56074 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQVv-0001GA-31
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:27 -0500
Received: from confino.investici.org ([93.190.126.19]:24921)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQVr-0001FY-Tk
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:24 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740611302;
 bh=0CJW+7GjjzzwUQp3vizp1adM7ydzLgcz9w4Ey/TZDwg=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=e9lHzaLOahYWE4WHrsUwhmpYM7skHuAiOEvhObsxc7+wXp2uU/a1TwHxsrFBDQ44a
 ErmUxmgPqZjhjQddiU+AnqvQ0IChIkDr4NGmjxvta9m9buDGCcqXx6L/kvPwJHGCLB
 PMo924DZPdcL2pApGSk99W0QYiEYuzTgQbI9ULdw=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z39BG46kBz11D5;
 Wed, 26 Feb 2025 23:08:22 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39BG2pscz111L; Wed, 26 Feb 2025 23:08:22 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v6 3/5] services: Add oci-service-type.
Date: Thu, 27 Feb 2025 00:08:08 +0100
Message-ID: <584a7fb04ad1be743e3935ea59aaec8883dbf0f4.1740611290.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
References: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 ++++++--
 gnu/services/containers.scm | 1314 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++-
 4 files changed, 2404 insertions(+), 252 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index a036c85c31a..fd08261af3b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -42325,59 +42325,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.  When a string is passed it will be interpreted as the
+absolute file-system path of the selected OCI runtime command.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -42397,16 +42500,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -42414,22 +42517,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -42457,7 +42562,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -42468,10 +42573,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -42482,25 +42588,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 24f31c756b8..603eec25e2b 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,8 +39,10 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +98,80 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-extension-wrap-validate
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -296,9 +370,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (eq? maybe-group #f)
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -347,6 +454,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -374,10 +486,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -392,7 +509,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -437,11 +566,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -451,9 +584,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -477,15 +610,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -514,7 +648,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -526,9 +660,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -541,71 +676,363 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invocation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invocation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (eq? runtime-cli #f)
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invocation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -614,24 +1041,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -642,14 +1056,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -662,7 +1073,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -677,113 +1089,665 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invokation runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invocation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run"
+    "--rm" "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp `(,(first invokation) ,@invokation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          (oci-container-run-invokation
+           runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments))
+         (wrap-command
+          (lambda (command)
+            (if (eq? runtime 'podman)
+                (list
+                 "/bin/sh" "-l" "-c"
+                 #~(string-join (list #$@command) " "))
+                command)))
+         (container-action
+          (lambda* (command #:key (environment-variables #f))
+            #~(lambda _
+                (fork+exec-command
+                 (list #$@command)
+                 #$@(if user (list #:user user) '())
+                 #$@(if group (list #:group group) '())
+                 #$@(if (maybe-value-set? log-file)
+                        (list #:log-file log-file)
+                        '())
+                 #$@(if (and user (eq? runtime 'podman))
+                        (list #:directory
+                              #~(passwd:dir (getpwnam #$user)))
+                        '())
+                 #$@(if environment-variables
+                        (list #:environment-variables
+                              environment-variables)
+                        '()))))))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
+                       (container-action
+                        (list (oci-container-entrypoint
+                               runtime runtime-cli name image image-reference
+                               invokation #:verbose? verbose?))
+                        #:environment-variables
+                        #~(append
+                           (list #$@host-environment)
+                           (list #$@runtime-environment))))
                       (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                       (container-action
+                        (wrap-command
+                         (list runtime-cli "rm" "-f" name))))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invokation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 (container-action
+                                  (wrap-command
+                                   (list runtime-cli "pull" image))))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invocation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  "Returns a list of Shepherd services based on the input OCI state."
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group
+                                      (oci-runtime-system-group runtime user group)
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subgids #f)
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subuids #f)
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((not (eq? runtime-cli #f))
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension subids-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
+                  (service-extension account-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
+                  (service-extension shepherd-root-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 828ceea313a..125e748bb0e 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -297,17 +302,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:08:27 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:08:27 2025
Received: from localhost ([127.0.0.1]:56072 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQVu-0001G4-KH
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:26 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:35199)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQVr-0001FV-T6
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:08:24 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740611301;
 bh=Q9g4XDsM/Xr0XzSKcV29fTA0IaYoCgcp06lbERRutEA=;
 h=From:To:Cc:Subject:Date:From;
 b=NA3tTbXdbNGF+MHeIbEwIrfVV/z9yYR5RE6yO6daf61V1hpTx0fTr7ysvQZDRKeJD
 fGIbpFf5n7uu5Uxj1ewh+DivURj/iEDDruoEY//N+a5iNW5Df1zRFv1aWL7nbsX/my
 Jn2vSYgUHNVqRXyQkzBP7fCv1ub6+W2feTMkkggw=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z39BF5F5Kz113q;
 Wed, 26 Feb 2025 23:08:21 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39BF4DbXz111L; Wed, 26 Feb 2025 23:08:21 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v6 1/5] services: rootless-podman: Use login shell.
Date: Thu, 27 Feb 2025 00:08:06 +0100
Message-ID: <0ba285c5241f6817250b4af71776072faf248a14.1740611290.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index b3cd109ce6c..d5a211765a6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 4f220482de742c9c03cf6378ab147026a330edd0
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 26 Feb 2025 23:01:24 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 26 18:01:24 2025
Received: from localhost ([127.0.0.1]:56036 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tnQP6-0000vR-4E
	for submit <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:01:24 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:28999)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tnQP2-0000vD-HY
 for 76081 <at> debbugs.gnu.org; Wed, 26 Feb 2025 18:01:21 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1740610877;
 bh=BRRJ7N+7EyK+1dKm1svgagZ5hIH13SpuoPh3X+g3lG0=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=G8OnlC6+zghRciOw5WhxH2a1W4De9DT825gkR51VIjkOu55EDfSee2mcjgl9Tawdv
 lauD+HtTd9biJ74XIU7faFHBV2Jyjxl+AQ9yThnCVthY4cB6/mIC2Nne3rV7if6Bz9
 IqQinMW5+O8Rq25zyiFf6ooTAQwIrDuyv2YKTFnc=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Z392534zWz11Ct
 for <76081 <at> debbugs.gnu.org>; Wed, 26 Feb 2025 23:01:17 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Z39252XlYz117r
 for <76081 <at> debbugs.gnu.org>; Wed, 26 Feb 2025 23:01:17 +0000 (UTC)
Message-ID: <397afc1f-b638-430a-b9e7-7de4721bca45@HIDDEN>
Date: Thu, 27 Feb 2025 00:01:16 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
 <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
Content-Language: en-US
In-Reply-To: <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi guix,

On 2/18/25 02:20, paul wrote:
> Hi,
>
> On 2/12/25 02:09, paul wrote:
>> Hi guix,
>>
>> On 2/9/25 21:38, paul wrote:
>>> I'm sending a v3 fixing a bug in the merge algorithm for volumes and 
>>> networks.
>>>
>>> On 2/9/25 20:14, paul wrote:
>>>> Hi,
>>>>
>>>> I'm about to send a v2. v2 compared to the first revision features:
>>>>
>>>>
>>>> - it actually compiles all the times :) (rev 1 referenced oci-image 
>>>> too early for it to be working and generated a compile time error, 
>>>> if you recompiled it sometimes went away so I thought it was a 
>>>> problem of my setup. CI caught this)
>>>> - it allows more values to be overridden by eventual users of the 
>>>> Scheme API
>>>> - it allows passing extra arguments directly after each podman or 
>>>> docker invokation, allowing for example for overriding podman 
>>>> --root and similar options.
>>>>
>>>> All of these tests should pass:
>>>>
>>>> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
>>>> oci-service-rootless-podman docker docker-system rootless-podman 
>>>> oci-service-docker"
>>>>
>> I'm sending a v4 changing slightly the image loader, the same tests 
>> as before are supposed to pass. Now the Home service [0] is working 
>> for me with rootless podman. I'll try it on different distros if I 
>> manage to.
>
> I'm sending a v5 implementing a Home service. The changes compared to 
> v4 are pretty trivial as the plumbing was already there, the only 
> downside is that I'm not able to use for-home? in 
> define-configuration, so I had to reimplement oci-configuration with 
> (guix records) and had to reimplement some validation (gnu services 
> configuration) would figure out magically.

I'm sending  a v6. Compared to v5 it resolves the conflicts with master 
and it should fix the stop action for rootless podman backed services.

Thank you for all your work,

giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 19 Feb 2025 00:10:01 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 18 19:10:00 2025
Received: from localhost ([127.0.0.1]:36274 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkXf3-0001RC-ED
	for submit <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:10:00 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:27437)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkXem-0001Oo-Pg
 for 76081 <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:47 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739923777;
 bh=C6uHhY9S9WpgPkIaaAgAOz8vooJK2RfngRRsEihvD9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=ELqQ4n5ofrNrD9inocf3DgK2/MlRIcqTCeC85GYYzdWpSOHvUxHfdsf/n0VDUx8Kr
 VIysE+OPLCYalnW49NabJfABbcTIarUcK0pJbMJqChsd8061BCG9DvNO/z97J4YtsM
 Bz0ikwxHMG7xuWEjcFEbj96TLzFFaLBMb5S9wovk=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YyGwd4pJgz110J;
 Wed, 19 Feb 2025 00:09:37 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YyGwd3KKkz10yt; Wed, 19 Feb 2025 00:09:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v5 2/5] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Wed, 19 Feb 2025 01:09:19 +0100
Message-ID: <cbdcfa5c8fcfbbbb4b843fc50b624171a5da5f6a.1739923762.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
References: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index dc66ac4f967..50bf6c05549 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -188,7 +237,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -242,3 +291,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 3af0f792701..69ff9f1d9e4 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -307,495 +295,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 19 Feb 2025 00:09:57 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 18 19:09:57 2025
Received: from localhost ([127.0.0.1]:36272 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkXf2-0001R7-Ez
	for submit <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:57 -0500
Received: from confino.investici.org ([93.190.126.19]:49497)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkXel-0001Ox-VZ
 for 76081 <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:42 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739923779;
 bh=sGIhirA8GRSvDy2Gv6T1Jnf2sTFHuYS1u49lb0uWaSw=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=Gr/AzrFcZGkIwgED2I0J/uM2v+VRK4w5CXcW8MJp49GX15k00C9Kz5/5LTF2jMdKP
 QuhyFVEikus4h4sa7fPhLuEMavGVB9WMqPncA/S79D5cgV4Cpdzhjh68pPqWR8Z3SH
 u18mPybA/ljK3YoRzYaEhKMwZvmr0qEPlaJmHvjA=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YyGwg0YkDz111j;
 Wed, 19 Feb 2025 00:09:39 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YyGwf6hycz10yt; Wed, 19 Feb 2025 00:09:38 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v5 5/5] home: Add home-oci-service-type.
Date: Wed, 19 Feb 2025 01:09:22 +0100
Message-ID: <ec9bac788ce132ac4e306e3e47cb9bd2849346f7.1739923762.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
References: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Andrew Tropin <andrew@HIDDEN>, Janneke Nieuwenhuizen <janneke@HIDDEN>, Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>, Tanguy Le Carrour <tanguy@HIDDEN>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

* gnu/home/service/containers.scm: New file;
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (OCI backed services): Document it.

Change-Id: I8ce5b301e8032d0a7b2a9ca46752738cdee1f030
---
 doc/guix.texi                    | 114 +++++++++++++++++++++++++++++++
 gnu/home/services/containers.scm |  50 ++++++++++++++
 gnu/local.mk                     |   1 +
 3 files changed, 165 insertions(+)
 create mode 100644 gnu/home/services/containers.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 0dfec66a52b..eacca409473 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -49826,6 +49826,120 @@ Miscellaneous Home Services
           (dicod-configuration @dots{})))
 @end lisp
 
+@subsubheading OCI backed services
+
+@cindex OCI-backed, for Home
+The @code{(gnu home services containers)} module provides the following service:
+
+@defvar home-oci-service-type
+This is the type of the service that allows to manage your OCI containers with
+the same consistent interface you use for your other Home Shepherd services.
+@end defvar
+
+This service is a direct mapping of the @code{oci-service-type} system
+service (@pxref{Miscellaneous Services, OCI backed services}).  You can
+use it like this:
+
+@lisp
+(use-modules (gnu services containers)
+             (gnu home services containers))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+
+@end lisp
+
+You may specify a custom configuration by providing a
+@code{oci-configuration} record, exactly like for
+@code{oci-service-type}, but wrapping it in @code{for-home}:
+
+@lisp
+(use-modules (gnu services)
+             (gnu services containers)
+             (gnu home services containers))
+
+(service home-oci-service-type
+         (for-home
+          (oci-configuration
+           (runtime 'podman)
+           (verbose? #t))))
+
+(simple-service 'home-oci-provisioning
+                home-oci-service-type
+                (oci-extension
+                  (volumes
+                    (list
+                      (oci-volume-configuration (name "prometheus"))
+                      (oci-volume-configuration (name "grafana"))))
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "monitoring")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090")))
+                      (volumes
+                       (list
+                        '(("prometheus" . "/var/lib/prometheus")))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "monitoring")
+                      (volumes
+                       '(("grafana:/var/lib/grafana"))))))))
+@end lisp
+
 @node Invoking guix home
 @section Invoking @command{guix home}
 
diff --git a/gnu/home/services/containers.scm b/gnu/home/services/containers.scm
new file mode 100644
index 00000000000..938dde2f37a
--- /dev/null
+++ b/gnu/home/services/containers.scm
@@ -0,0 +1,50 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services containers)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:use-module (gnu services)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services containers)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (srfi srfi-1)
+  #:export (home-oci-service-type))
+
+(define home-oci-service-type
+  (service-type (inherit (system->home-service-type oci-service-type))
+                (extensions
+                 (list
+                  (service-extension home-profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension home-shepherd-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                (extend
+                 (lambda (config extension)
+                   (for-home
+                    (oci-configuration
+                     (inherit (oci-configuration-extend config extension))))))
+                (default-value (for-home (oci-configuration)))))
diff --git a/gnu/local.mk b/gnu/local.mk
index 01926bb1b8b..37ab59a3ba1 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -102,6 +102,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home.scm					\
   %D%/home/services.scm			\
   %D%/home/services/admin.scm			\
+  %D%/home/services/containers.scm		\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
-- 
2.48.1





Information forwarded to andrew@HIDDEN, janneke@HIDDEN, ludo@HIDDEN, maxim.cournoyer@HIDDEN, tanguy@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 19 Feb 2025 00:09:56 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 18 19:09:56 2025
Received: from localhost ([127.0.0.1]:36270 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkXf1-0001Qy-Kc
	for submit <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:56 -0500
Received: from confino.investici.org ([93.190.126.19]:38953)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkXel-0001Ov-Iu
 for 76081 <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:41 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739923778;
 bh=Wfjy2Y2ODngRJo5q3H8fRebODJqxLcwsieCh5T5hHoA=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=NO/pbIAEEzb1QQ+zkx6Gdd8+cWNIMTz6rHvI/yFYPNltaPELr1EDj2JxPiMtpnyuS
 dKAuBNyR2PtBihqzFrfV4UxBD50byvOEythxsvEwTWYMy5cT5sxmqkS4INfvrRL/Zy
 6pyXxk2712Rr/2ISS9AJ9ZdkzVSTfZwszl8fN4Eo=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YyGwf4hPDz111d;
 Wed, 19 Feb 2025 00:09:38 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YyGwf3kmqz10yt; Wed, 19 Feb 2025 00:09:38 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v5 4/5] tests: Use lower-oci-image-state in container tests.
Date: Wed, 19 Feb 2025 01:09:21 +0100
Message-ID: <3fa0aa07079ab537bdd5b214803d56e08c426092.1739923762.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
References: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index bd1b6f14356..8b57ac57798 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -75,6 +75,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 5e6f39387e7..8cdd86e7ae3 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 19 Feb 2025 00:09:44 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 18 19:09:44 2025
Received: from localhost ([127.0.0.1]:36266 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkXeo-0001Pm-U0
	for submit <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:44 -0500
Received: from confino.investici.org ([93.190.126.19]:31491)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkXel-0001On-7w
 for 76081 <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:40 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739923777;
 bh=j21R+eL7G7bstOL1dDr/h5ZqTwKB6M0H90ec7qezBbQ=;
 h=From:To:Cc:Subject:Date:From;
 b=hn0IWfcTncp7U1z7vTRv+nnwZmWsD12xBd0sSRqzYvqMbYZZCGc5KO543QHkO8R2F
 zQVd/rE2AJyxH4gHq8i+mAwGbW3Rl2PGIOeA1CrWJ8/Ye1rAvcUXa5YxWd9jI3tG8h
 djkdM09EDBF4rcas+XBg4hD9RJEH8VbYitnCiQww=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YyGwd1gT7z1107;
 Wed, 19 Feb 2025 00:09:37 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YyGwd0hkvz10yt; Wed, 19 Feb 2025 00:09:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v5 1/5] services: rootless-podman: Use login shell.
Date: Wed, 19 Feb 2025 01:09:18 +0100
Message-ID: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 19d35ccbcb6..dc66ac4f967 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 3dc8026d58f9480547a595450a6483e0f13c1ba4
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 19 Feb 2025 00:09:43 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 18 19:09:42 2025
Received: from localhost ([127.0.0.1]:36265 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkXeo-0001Pi-Oo
	for submit <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:42 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:46549)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkXel-0001Or-WF
 for 76081 <at> debbugs.gnu.org; Tue, 18 Feb 2025 19:09:40 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739923778;
 bh=QyhhODzAfom/nmalVB1vs4ijsSh9GdUnM5Z3Zv2yCAA=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=Pk0WSmaX7DvEWuf+f1MHECe9YA9kDKUNObKBelziRJ3rJ31HDZ7ABK5kXi4A+UKQr
 96YWGVWO0EF7vvRAm4HVN0dCOMKvQoMsOtL6EzsEUyLx0AF2a3p3GRF3S+ZycWTR8u
 TvhJC7G/PYWIWD0z8jnRQ9eOp6sp9NSV7jmRLiug=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YyGwf1nK5z1112;
 Wed, 19 Feb 2025 00:09:38 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YyGwd6mXQz10yt; Wed, 19 Feb 2025 00:09:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v5 3/5] services: Add oci-service-type.
Date: Wed, 19 Feb 2025 01:09:20 +0100
Message-ID: <a12ad3347114d37c91ebba6820654b46da31e21b.1739923762.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
References: <6d774afae43df08bf52f7ecb8900d35f501280d8.1739923762.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  306 +++++++--
 gnu/services/containers.scm | 1283 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 ++++++++++++++++++++++++++-
 4 files changed, 2380 insertions(+), 245 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 54a736c518c..0dfec66a52b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -42282,59 +42282,162 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package-or-string)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.  When a string is passed it will be interpreted as the
+absolute file-system path of the selected OCI runtime command.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -42354,16 +42457,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -42371,22 +42474,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -42414,7 +42519,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -42425,10 +42530,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -42439,25 +42545,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 50bf6c05549..bd1b6f14356 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -39,8 +39,10 @@ (define-module (gnu services containers)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix records)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +98,80 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-home-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+            oci-configuration-valid?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-container-shepherd-name
+            oci-networks-shepherd-name
+            oci-networks-home-shepherd-name
+            oci-volumes-shepherd-name
+            oci-volumes-home-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-extension-merge
+            oci-service-extension-wrap-validate
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services
+            oci-configuration-extend))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -294,9 +368,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (eq? maybe-group #f)
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")
+      maybe-group))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -345,6 +452,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -372,10 +484,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -390,7 +507,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -435,11 +564,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -449,9 +582,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -475,15 +608,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -512,7 +646,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -524,9 +658,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -539,71 +674,363 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invokation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-record-type* <oci-configuration>
+  oci-configuration
+  make-oci-configuration
+  oci-configuration?
+  this-oci-configuration
+
+  (runtime                  oci-configuration-runtime
+                            (default 'docker))
+  (runtime-cli              oci-configuration-runtime-cli
+                            (default #f))                               ; package or string
+  (runtime-extra-arguments  oci-configuration-runtime-extra-arguments   ; strings or gexps
+                            (default '()))                              ; or file-like objects
+  (user                     oci-configuration-user
+                            (default "oci-container"))
+  (group                    oci-configuration-group                     ; string
+                            (default #f))
+  (subuids-range            oci-configuration-subuids-range             ; subid-range
+                            (default #f))
+  (subgids-range            oci-configuration-subgids-range             ; subid-range
+                            (default #f))
+  (containers               oci-configuration-containers                ; oci-container-configurations
+                            (default '()))
+  (networks                 oci-configuration-networks                  ; oci-network-configurations
+                            (default '()))
+  (volumes                  oci-configuration-volumes                   ; oci-volume-configurations
+                            (default '()))
+  (verbose?                 oci-configuration-verbose?
+                            (default #f))
+  (home-service?            oci-configuration-home-service?
+                            (default for-home?) (innate)))
+
+(define (package-or-string? value)
+  (or (package? value) (string? value)))
+
+(define (oci-configuration-valid? config)
+  (define runtime-cli
+    (oci-configuration-runtime-cli config))
+  (define group
+    (oci-configuration-group config))
+  (define subuids-range
+    (oci-configuration-subuids-range config))
+  (define subgids-range
+    (oci-configuration-subgids-range config))
+  (and
+   (symbol?
+    (oci-sanitize-runtime (oci-configuration-runtime config)))
+   (or (eq? runtime-cli #f)
+       (package-or-string? runtime-cli))
+   (list? (oci-configuration-runtime-extra-arguments config))
+   (string? (oci-configuration-user config))
+   (or (eq? group #f)
+       (string? group))
+   (or (eq? subuids-range #f)
+       (subid-range? subuids-range))
+   (or (eq? subgids-range #f)
+       (subid-range? subgids-range))
+   (list-of-oci-containers?
+    (oci-configuration-containers config))
+   (list-of-oci-networks?
+    (oci-configuration-networks config))
+   (list-of-oci-volumes?
+    (oci-configuration-volumes config))
+   (boolean?
+    (oci-configuration-verbose? config))
+   (boolean?
+    (oci-configuration-home-service? config))))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (eq? runtime-cli #f)
+               path
+               runtime-cli)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define (oci-runtime-home-cli config)
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli
+                     (string-append (getenv "HOME")
+                                    "/.guix-home/profile"))))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invokation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-networks-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-networks-shepherd-name runtime)))
+
+(define (oci-volumes-home-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Home Shepherd service based on
+RUNTIME."
+  (string-append "home-" (oci-volumes-shepherd-name runtime)))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -612,24 +1039,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -640,14 +1054,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -660,7 +1071,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -675,113 +1087,648 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invokation runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invokation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run"
+    "--rm" "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp `(,(first invokation) ,@invokation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          (oci-container-run-invokation
+           runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments)))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
                        #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
                            (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
+                            (list
+                             #$(oci-container-entrypoint
+                                runtime runtime-cli name image image-reference
+                                invokation #:verbose? verbose?))
+                            #$@(if user (list #:user user) '())
+                            #$@(if group (list #:group group) '())
                             #$@(if (maybe-value-set? log-file)
                                    (list #:log-file log-file)
                                    '())
+                            #$@(if (and user (eq? runtime 'podman))
+                                   (list #:directory
+                                         #~(passwd:dir (getpwnam #$user)))
+                                   '())
                             #:environment-variables
-                            (list #$@host-environment))))
+                            (append
+                             (list #$@host-environment)
+                             (list #$@runtime-environment)))))
                       (stop
                        #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                           (invoke #$runtime-cli "rm" "-f" #$name)))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invokation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 #~(lambda _
+                                     (invoke #$runtime-cli "pull" #$image)))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invokation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  (let* ((networks-name
+          (if (string? networks-name)
+              networks-name
+              (oci-networks-shepherd-name runtime)))
+         (networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol networks-name))
+              '()))
+         (volumes-name
+          (if (string? volumes-name)
+              volumes-name
+              (oci-volumes-shepherd-name runtime)))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list (string->symbol volumes-name))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           networks-name networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           volumes-name volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (system-runtime-cli
+          (oci-runtime-system-cli config))
+         (home-runtime-cli
+          (oci-runtime-home-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config))
+         (home-service?
+          (oci-configuration-home-service? config)))
+    (if home-service?
+        (oci-state->shepherd-services runtime home-runtime-cli containers networks volumes
+                                      #:verbose? verbose?
+                                      #:networks-name
+                                      (oci-networks-home-shepherd-name runtime)
+                                      #:volumes-name
+                                      (oci-volumes-home-shepherd-name runtime))
+        (oci-state->shepherd-services runtime system-runtime-cli containers networks volumes
+                                      #:user user
+                                      #:group
+                                      (oci-runtime-system-group runtime user group)
+                                      #:verbose? verbose?
+                                      #:runtime-extra-arguments
+                                      runtime-extra-arguments
+                                      #:runtime-environment
+                                      (oci-runtime-system-environment runtime user)
+                                      #:runtime-requirement
+                                      (oci-runtime-system-requirement runtime)
+                                      #:networks-requirement '(networking)))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subgids #f)
+          (subid-range (name user))
+          subgids)
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (eq? subuids #f)
+          (subid-range (name user))
+          subuids)
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  `(,bash-minimal
+    ,@(if (string? runtime-cli)
+          '()
+          (list
+           (cond
+            ((not (eq? runtime-cli #f))
+             runtime-cli)
+            ((eq? 'podman runtime)
+             podman)
+            (else
+             docker-cli))))))
+
+(define (oci-service-extension-wrap-validate extension)
+  (lambda (config)
+    (if (oci-configuration-valid? config)
+        (extension config)
+        (raise
+         (formatted-message
+          (G_ "Invalide oci-configuration ~a.") config)))))
+
+(define (oci-configuration-extend config extension)
+  (oci-configuration
+   (inherit config)
+   (containers
+    (oci-objects-merge-lst
+     (oci-configuration-containers config)
+     (oci-extension-containers extension)
+     "container"
+     (lambda (oci-config)
+       (define runtime
+         (oci-configuration-runtime config))
+       (oci-container-shepherd-name runtime oci-config))))
+   (networks (oci-objects-merge-lst
+              (oci-configuration-networks config)
+              (oci-extension-networks extension)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-configuration-volumes config)
+             (oci-extension-volumes extension)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (oci-service-extension-wrap-validate
+                                      (lambda (config)
+                                        (let ((runtime-cli
+                                               (oci-configuration-runtime-cli config))
+                                              (runtime
+                                               (oci-configuration-runtime config)))
+                                          (oci-service-profile runtime runtime-cli)))))
+                  (service-extension subids-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-subids))
+                  (service-extension account-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-service-accounts))
+                  (service-extension shepherd-root-service-type
+                                     (oci-service-extension-wrap-validate
+                                      oci-configuration->shepherd-services))))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend oci-configuration-extend)
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 69ff9f1d9e4..98ee175873d 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -295,17 +300,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..5e6f39387e7 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 18 Feb 2025 01:20:20 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon Feb 17 20:20:20 2025
Received: from localhost ([127.0.0.1]:51706 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tkCHb-0006w2-II
	for submit <at> debbugs.gnu.org; Mon, 17 Feb 2025 20:20:20 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:40445)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tkCHY-0006vl-Vv
 for 76081 <at> debbugs.gnu.org; Mon, 17 Feb 2025 20:20:18 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739841613;
 bh=bwl29Q9kAgjn0YN55v/Axxmnk25Kq11SvA9YVZ1yZmE=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=sndP5/G7csVWJPmdmUnESvpPjDF7Hbj2QPqrd5KRc6Fnn/RxJI+1Ds6K7Zsn8mtYW
 GRg4Z39BrOtq3X0i1NwBkYDxEsFMEQKoAfEv1LneZHEtcPar8VxZA/pbBZPQ63gegv
 /ItqnvUj5fLo7ZVrow87RT/a8onGCngJRSUSBbSY=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YxhXY2s94z111D
 for <76081 <at> debbugs.gnu.org>; Tue, 18 Feb 2025 01:20:13 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YxhXY2KFFz1119
 for <76081 <at> debbugs.gnu.org>; Tue, 18 Feb 2025 01:20:13 +0000 (UTC)
Message-ID: <da407350-a2e1-482b-a034-ddead598fa6b@HIDDEN>
Date: Tue, 18 Feb 2025 02:20:12 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
 <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
Content-Language: en-US
In-Reply-To: <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

On 2/12/25 02:09, paul wrote:
> Hi guix,
>
> On 2/9/25 21:38, paul wrote:
>> I'm sending a v3 fixing a bug in the merge algorithm for volumes and 
>> networks.
>>
>> On 2/9/25 20:14, paul wrote:
>>> Hi,
>>>
>>> I'm about to send a v2. v2 compared to the first revision features:
>>>
>>>
>>> - it actually compiles all the times :) (rev 1 referenced oci-image 
>>> too early for it to be working and generated a compile time error, 
>>> if you recompiled it sometimes went away so I thought it was a 
>>> problem of my setup. CI caught this)
>>> - it allows more values to be overridden by eventual users of the 
>>> Scheme API
>>> - it allows passing extra arguments directly after each podman or 
>>> docker invokation, allowing for example for overriding podman --root 
>>> and similar options.
>>>
>>> All of these tests should pass:
>>>
>>> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
>>> oci-service-rootless-podman docker docker-system rootless-podman 
>>> oci-service-docker"
>>>
> I'm sending a v4 changing slightly the image loader, the same tests as 
> before are supposed to pass. Now the Home service [0] is working for 
> me with rootless podman. I'll try it on different distros if I manage to.

I'm sending a v5 implementing a Home service. The changes compared to v4 
are pretty trivial as the plumbing was already there, the only downside 
is that I'm not able to use for-home? in define-configuration, so I had 
to reimplement oci-configuration with (guix records) and had to 
reimplement some validation (gnu services configuration) would figure 
out magically.

Thank you for your work and time,

cheers
giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 12 Feb 2025 01:10:59 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 11 20:10:59 2025
Received: from localhost ([127.0.0.1]:59709 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1ti1HE-0008Qi-RT
	for submit <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:59 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:50425)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1ti1H7-0008Pk-HR
 for 76081 <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:52 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739322646;
 bh=C6uHhY9S9WpgPkIaaAgAOz8vooJK2RfngRRsEihvD9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=eZLrg0VhR1XMzVfVBENPbKgsQccHcuiQ6tBwTvnZJOIdVkjZks0dzD1//YjfBu+vP
 abuObaNITFI8LTfBIvU+CYtj05EoWaFx7/PgmJ0Y1sjIQmScYv121yfDKMIQy2NYP7
 e3EcIh81yHctJsOgaHp6d9WeR6wweP5vN0iIYPaA=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yt0cQ5D8Cz111X;
 Wed, 12 Feb 2025 01:10:46 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yt0cQ3yfpz110r; Wed, 12 Feb 2025 01:10:46 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v4 2/4] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Wed, 12 Feb 2025 02:10:35 +0100
Message-ID: <77229c8777b0b074a44bd848b60140a49b621ac6.1739322637.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
References: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index dc66ac4f967..50bf6c05549 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -188,7 +237,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -242,3 +291,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 3af0f792701..69ff9f1d9e4 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -307,495 +295,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 12 Feb 2025 01:10:56 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 11 20:10:56 2025
Received: from localhost ([127.0.0.1]:59707 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1ti1HE-0008Qa-52
	for submit <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:56 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:56991)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1ti1H7-0008Pj-4w
 for 76081 <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:49 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739322646;
 bh=DYecIs2DDQmcg9DDiyTdZ/3TSj9SQGRkZLq3/7QxiFk=;
 h=From:To:Cc:Subject:Date:From;
 b=cHvqoIqwjUmD5xY/KdvEaGtY4ys4a4rERrlgwsnvh997uEfgZtKjr6+dak67Cjm6m
 3snWhVJthn5EWarR+QC1U0xYrgvspDyuabDjn9XgraFWavOYE0WSfVyBwiKJyAVWKg
 Ccsp4Lfo/z+0lTJBj7LA+QtYepa5YXbhQdgpPFG0=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yt0cQ2PvMz110q;
 Wed, 12 Feb 2025 01:10:46 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yt0cQ1SHPz111Z; Wed, 12 Feb 2025 01:10:46 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v4 1/4] services: rootless-podman: Use login shell.
Date: Wed, 12 Feb 2025 02:10:34 +0100
Message-ID: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 19d35ccbcb6..dc66ac4f967 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: f650fc3e15e0ba04b3998daf13db8666fcfd9627
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 12 Feb 2025 01:10:56 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 11 20:10:55 2025
Received: from localhost ([127.0.0.1]:59705 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1ti1HD-0008QQ-3n
	for submit <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:55 -0500
Received: from confino.investici.org ([93.190.126.19]:46585)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1ti1H6-0008Pn-Dq
 for 76081 <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:49 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739322647;
 bh=HMW90ejxBeH5UYDjCNV/eguiuFUBPfKqWYkFSGGNlzI=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=VwdgjMHxOKB1GjaIz4CNPaWHzyhgVOwFV8dWAvjMgf8Tf4fOM0rut8ILsNUHvxcFg
 sBlVAmEB1x7c02k2f6iNmvizqIEqiXd5vQQMIdy+9QQosnp7AUc/ypI7WKBsVQnb/5
 iOhVWc9a1konNr0fwF8vAoU6byRQO3g2Qs8UkqlY=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yt0cR3jfRz1123;
 Wed, 12 Feb 2025 01:10:47 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yt0cR2mwGz110r; Wed, 12 Feb 2025 01:10:47 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v4 4/4] tests: Use lower-oci-image-state in container tests.
Date: Wed, 12 Feb 2025 02:10:37 +0100
Message-ID: <ca35d6b7caceb4e2dd3db863b9b7c1601f3ee0cf.1739322637.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
References: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |   2 +
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 95 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 018dfa60f48..63e9cb37b53 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index bac1f47bd34..1fcf6be7ace 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 12 Feb 2025 01:10:51 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 11 20:10:51 2025
Received: from localhost ([127.0.0.1]:59704 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1ti1H8-0008Q6-Sg
	for submit <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:50 -0500
Received: from confino.investici.org ([93.190.126.19]:24407)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1ti1H6-0008Pl-23
 for 76081 <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:10:48 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739322647;
 bh=Nyfej6pMLQ1200H5wa4lySDmqxHy9uNI4Nz1FDafHGg=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=lnZV+k2UV9AAJaFuz8wKJlC8eU+kjPyWg7S4/L+5os/L4gUVYWHNfYhx3sEQukuPV
 RMH9UV7blpE3V/62gpigY4SSjvYv+VNGZ/7SaGyKq6k9iBU98jLRywY8Bs+Gtschl+
 sm3IQA9Usy+4iPr44ZEWOWdN6DAiS852BAzTLz0Q=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yt0cR1H52z111Z;
 Wed, 12 Feb 2025 01:10:47 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yt0cR01fxz110r; Wed, 12 Feb 2025 01:10:46 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v4 3/4] services: Add oci-service-type.
Date: Wed, 12 Feb 2025 02:10:36 +0100
Message-ID: <30398d43f3b262022c3511187fe92296b00667ce.1739322637.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
References: <3e199fa3b6b15d853ea1887ad0c518c7cb4f4b25.1739322637.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  305 +++++++--
 gnu/services/containers.scm | 1217 ++++++++++++++++++++++++++++++-----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++++-
 4 files changed, 2313 insertions(+), 245 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index ce780682ed0..500cf9f839c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -41872,59 +41872,161 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (type: maybe-string)
+The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.
+
+@item @code{group} (type: maybe-string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -41944,16 +42046,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -41961,22 +42063,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -42004,7 +42108,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -42015,10 +42119,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -42029,25 +42134,97 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
-A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 50bf6c05549..018dfa60f48 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,73 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-fields
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-networks-shepherd-name
+            oci-volumes-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-objects-merge-lst
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -294,9 +360,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (maybe-value-set? maybe-group)
+      maybe-group
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -345,6 +444,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -372,10 +476,15 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
+  "Return a string OCI image reference representing IMAGE."
+  (define reference
+    (if (string? image)
+        image
+        (string-append (oci-image-repository image)
+                       ":" (oci-image-tag image))))
+  (if (> (length (string-split reference #\/)) 1)
+        reference
+        (string-append "localhost/" reference)))
 
 (define (oci-lowerable-image? image)
   (or (manifest? image)
@@ -390,7 +499,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -435,11 +556,15 @@ (define-configuration/no-serialization oci-image
 
 (define-configuration/no-serialization oci-container-configuration
   (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   (maybe-string)
+   "The user name under whose authority OCI commands will be run.  This field will
+override the @code{user} field of @code{oci-configuration}.")
   (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+This field will override the @code{group} field of @code{oci-configuration}.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -449,9 +574,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -475,15 +600,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -512,7 +638,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -524,9 +650,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -539,71 +666,331 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invokation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker volume create} or @command{podman volume create}
+invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker network create} or @command{podman network create}
+invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.")
+  (runtime-extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (maybe-value-set? runtime-cli)
+               runtime-cli
+               path)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invokation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -612,24 +999,11 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -640,14 +1014,11 @@ (define (lower-manifest name image target system)
        (tarball (apply pack:docker-image
                        `(,name ,profile
                          ,@options
-                         ,@image-tag
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -660,7 +1031,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -675,113 +1047,622 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
+(define (oci-object-exists? runtime runtime-cli object verbose?)
+  #~(lambda* (name #:key (format-string "{{.Name}}"))
+      (use-modules (ice-9 format)
+                   (ice-9 match)
+                   (ice-9 popen)
+                   (ice-9 rdelim)
+                   (srfi srfi-1))
+
+      (define (read-lines file-or-port)
+        (define (loop-lines port)
+          (let loop ((lines '()))
+            (match (read-line port)
+              ((? eof-object?)
+               (reverse lines))
+              (line
+               (loop (cons line lines))))))
+
+        (if (port? file-or-port)
+            (loop-lines file-or-port)
+            (call-with-input-file file-or-port
+              loop-lines)))
+
+      #$(if (eq? runtime 'podman)
+            #~(let ((command
+                     (list #$runtime-cli
+                           #$object "exists" name)))
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" command))
+                (define exit-code (status:exit-val (apply system* command)))
+                (when #$verbose?
+                  (format #t "Exit code: ~a~%" exit-code))
+                (equal? EXIT_SUCCESS exit-code))
+            #~(let ((command
+                     (string-append #$runtime-cli
+                                    " " #$object " ls --format "
+                                    "\"" format-string "\"")))
+                (when #$verbose?
+                  (format #t "Running ~a~%" command))
+                (member name (read-lines (open-input-pipe command)))))))
+
+(define* (oci-image-loader runtime runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
+      (program-file
+       (format #f "~a-image-loader" name)
        #~(begin
            (use-modules (guix build utils)
+                        (ice-9 match)
                         (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+                        (ice-9 rdelim)
+                        (srfi srfi-1))
+           (define object-exists?
+             #$(oci-object-exists? runtime runtime-cli "image" verbose?))
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+
+           (if (object-exists? #$tag #:format-string "{{.Repository}}:{{.Tag}}")
+               (format #t "~a image already exists, skipping.~%" #$tag)
+               (begin
+                 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+                 (when #$verbose?
+                   (format #t "Running ~a~%" load-command))
+                 (let ((line (read-line
+                              (open-input-pipe load-command))))
+                   (unless (or (eof-object? line)
+                               (string-null? line))
+                     (format #t "~a~%" line)
+                     (let* ((repository&tag
+                             (string-drop line
+                                          (string-length
+                                           "Loaded image: ")))
+                            (tag-command
+                             (list #$runtime-cli "tag" repository&tag #$tag))
+                            (drop-old-tag-command
+                             (list #$runtime-cli "image" "rm" "-f" repository&tag)))
+
+                       (unless (string=? repository&tag #$tag)
+                         (when #$verbose?
+                           (format #t "Running~{ ~a~}~%" tag-command))
+
+                         (let ((exit-code
+                                (status:exit-val (apply system* tag-command))))
+                           (format #t "Tagged ~a with ~a...~%" #$tarball #$tag)
+
+                           (when #$verbose?
+                             (format #t "Exit code: ~a~%" exit-code))
+
+                           (when (equal? EXIT_SUCCESS exit-code)
+                             (when #$verbose?
+                               (format #t "Running~{ ~a~}~%" drop-old-tag-command))
+                             (let ((drop-exit-code
+                                    (status:exit-val (apply system* drop-old-tag-command))))
+                               (when #$verbose?
+                                 (format #t "Exit code: ~a~%" drop-exit-code))))))))))))))))
+
+(define (oci-container-run-invokation runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invokation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run"
+    "--rm" "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp `(,(first invokation) ,@invokation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (maybe-user (oci-container-configuration-user config))
+         (maybe-group (oci-container-configuration-group config))
+         (user (if (maybe-value-set? maybe-user)
+                    maybe-user
+                    user))
+         (group (if (maybe-value-set? maybe-group)
+                    maybe-group
+                    group))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          (oci-container-run-invokation
+           runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments)))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
                        #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
                            (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
+                            (list
+                             #$(oci-container-entrypoint
+                                runtime runtime-cli name image image-reference
+                                invokation #:verbose? verbose?))
+                            #$@(if user (list #:user user) '())
+                            #$@(if group (list #:group group) '())
                             #$@(if (maybe-value-set? log-file)
                                    (list #:log-file log-file)
                                    '())
+                            #$@(if (and user (eq? runtime 'podman))
+                                   (list #:directory
+                                         #~(passwd:dir (getpwnam #$user)))
+                                   '())
                             #:environment-variables
-                            (list #$@host-environment))))
+                            (append
+                             (list #$@host-environment)
+                             (list #$@runtime-environment)))))
                       (stop
                        #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                           (invoke #$runtime-cli "rm" "-f" #$name)))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invokation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 #~(lambda _
+                                     (invoke #$runtime-cli "pull" #$image)))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invokation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define object-exists?
+         #$(oci-object-exists? runtime runtime-cli object verbose?))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  (let* ((networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol
+                (oci-networks-shepherd-name runtime)))
+              '()))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list
+               (string->symbol
+                (oci-volumes-shepherd-name runtime)))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           (if (string? networks-name)
+               networks-name
+               (oci-networks-shepherd-name runtime))
+           networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           (if (string? volumes-name)
+               volumes-name
+               (oci-volumes-shepherd-name runtime))
+           volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (runtime-cli
+          (oci-runtime-system-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                  #:user user
+                                  #:group
+                                  (oci-runtime-system-group runtime user group)
+                                  #:verbose? verbose?
+                                  #:runtime-extra-arguments
+                                  runtime-extra-arguments
+                                  #:runtime-environment
+                                  (oci-runtime-system-environment runtime user)
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime)
+                                  #:networks-requirement '(networking))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range)
+              (and (maybe-value-set?
+                    (subid-range-name range))
+                   (not (string=? (subid-range-name range) user))))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subgids)
+          subgids
+          (subid-range (name user)))
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subuids)
+          subuids
+          (subid-range (name user)))
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  (list bash-minimal
+        (cond
+         ((maybe-value-set? runtime-cli)
+          runtime-cli)
+         ((eq? 'podman runtime)
+          podman)
+         (else
+          docker-cli))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend
+                 (lambda (config extension)
+                   (oci-configuration
+                    (inherit config)
+                    (containers
+                     (oci-objects-merge-lst
+                      (oci-configuration-containers config)
+                      (oci-extension-containers extension)
+                      "container"
+                      (lambda (oci-config)
+                        (define runtime
+                          (oci-configuration-runtime config))
+                        (oci-container-shepherd-name runtime oci-config))))
+                    (networks (oci-objects-merge-lst
+                               (oci-configuration-networks config)
+                               (oci-extension-networks extension)
+                               "network"
+                               oci-network-configuration-name))
+                    (volumes (oci-objects-merge-lst
+                              (oci-configuration-volumes config)
+                              (oci-extension-volumes extension)
+                              "volume"
+                              oci-volume-configuration-name)))))
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 69ff9f1d9e4..98ee175873d 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -295,17 +300,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..bac1f47bd34 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                 #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 12 Feb 2025 01:09:34 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Feb 11 20:09:34 2025
Received: from localhost ([127.0.0.1]:59694 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1ti1Fu-0008HP-2J
	for submit <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:09:34 -0500
Received: from confino.investici.org ([93.190.126.19]:37533)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1ti1Fp-0008HB-Df
 for 76081 <at> debbugs.gnu.org; Tue, 11 Feb 2025 20:09:31 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739322566;
 bh=wSqxT0AmfHZkcDc+nhirWeLwhKyq+LbuBpzlLwwOb/4=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=YRQ0GmhaHK2jzTviN38eP6Y3/VF1vOtMfzSi8xJsfm5rhrFkOQ81BQe7XSIy7RchZ
 8q1pL0I5jktSJQCMTq+i03GUUSCsXqYvdYBSYtQdLFSfT8QNN58DEgkn4z4myCKHws
 MWitMvXbJWLzRysZg0K25y3TLa4+KuSKW9W/4bgw=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yt0Zt65yrz110r
 for <76081 <at> debbugs.gnu.org>; Wed, 12 Feb 2025 01:09:26 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yt0Zt5ZYRz110q
 for <76081 <at> debbugs.gnu.org>; Wed, 12 Feb 2025 01:09:26 +0000 (UTC)
Message-ID: <274b98ea-1af9-4f0f-af58-8fa755feb73b@HIDDEN>
Date: Wed, 12 Feb 2025 02:09:26 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
 <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
Content-Language: en-US
In-Reply-To: <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

Hi guix,

On 2/9/25 21:38, paul wrote:
> I'm sending a v3 fixing a bug in the merge algorithm for volumes and 
> networks.
>
> On 2/9/25 20:14, paul wrote:
>> Hi,
>>
>> I'm about to send a v2. v2 compared to the first revision features:
>>
>>
>> - it actually compiles all the times :) (rev 1 referenced oci-image 
>> too early for it to be working and generated a compile time error, if 
>> you recompiled it sometimes went away so I thought it was a problem 
>> of my setup. CI caught this)
>> - it allows more values to be overridden by eventual users of the 
>> Scheme API
>> - it allows passing extra arguments directly after each podman or 
>> docker invokation, allowing for example for overriding podman --root 
>> and similar options.
>>
>> All of these tests should pass:
>>
>> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
>> oci-service-rootless-podman docker docker-system rootless-podman 
>> oci-service-docker"
>>
I'm sending a v4 changing slightly the image loader, the same tests as 
before are supposed to pass. Now the Home service [0] is working for me 
with rootless podman. I'll try it on different distros if I manage to.


Thank you for your work,

giacomo


[0]: 
https://github.com/fishinthecalculator/gocix/blob/main/modules/oci/home/services/containers.scm





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 20:39:28 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 15:39:27 2025
Received: from localhost ([127.0.0.1]:46582 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thE5N-0006y2-9l
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:27 -0500
Received: from confino.investici.org ([93.190.126.19]:38057)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thE5E-0006x9-MH
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:19 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739133555;
 bh=C6uHhY9S9WpgPkIaaAgAOz8vooJK2RfngRRsEihvD9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=VZW7tY6crAquN7vL2SdjvCvrI/b5weQ5iTbTi5ODPH5RFQwWkbYFMJiaRDirrRMnB
 JGThGPmrTdlheBB906444KgMBl/krbsasxKA7oo8egdQCwB4UYrOx5h7RWwOwL8qEY
 hiAekJKF80hHJYEJHF2itPqd3wHIqn8uYVyODjAc=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yrfh34q93z112f;
 Sun,  9 Feb 2025 20:39:15 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yrfh337JDz112H; Sun,  9 Feb 2025 20:39:15 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v3 2/4] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Sun,  9 Feb 2025 21:39:06 +0100
Message-ID: <92f19ba6c0842885735dfce653eef535360085f4.1739133548.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index dc66ac4f967..50bf6c05549 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -188,7 +237,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -242,3 +291,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 3af0f792701..69ff9f1d9e4 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -307,495 +295,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 20:39:25 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 15:39:25 2025
Received: from localhost ([127.0.0.1]:46580 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thE5M-0006xv-Es
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:25 -0500
Received: from confino.investici.org ([93.190.126.19]:52013)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thE5F-0006xB-Fa
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:18 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739133556;
 bh=OR92uJtdqeJZb5/tRylm2hBiS5mxpqify+pKZXZURS4=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=r4Dn3TDzSIzdCh7y8hLkW1b3zKikkvP2FTI8+BQEEFiRxq9s4GTP8zivrpT778ZoL
 MMnT4xuvWzXVhHjXSfZxuLtwqt2eju80MQbqSC2Ub4yX6QfnwCtoT6KSa9arPK6x0I
 9M9Csketc9jzuehjy0QVLt7DBsrRawXVml9nrwfM=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yrfh448Kkz112p;
 Sun,  9 Feb 2025 20:39:16 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yrfh436jqz112H; Sun,  9 Feb 2025 20:39:16 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v3 4/4] tests: Use lower-oci-image-state in container tests.
Date: Sun,  9 Feb 2025 21:39:08 +0100
Message-ID: <de9951c72836295e9eae310c2e19b372982915b3.1739133548.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |  35 ++++++++-----
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 114 insertions(+), 101 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 12c509c07ab..5979b26e6c9 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
@@ -996,12 +998,9 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  "Lower IMAGE, a manifest record, into a tarball containing an OCI image."
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (define image-tag
     (let* ((extra-options
             (get-keyword-value options #:extra-options))
@@ -1013,8 +1012,7 @@ (define (lower-manifest name image target system)
           `(#:extra-options (#:image-tag ,image-reference)))))
 
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -1029,11 +1027,8 @@ (define (lower-manifest name image target system)
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -1046,7 +1041,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -1061,6 +1057,17 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
 (define* (oci-image-loader runtime-cli name image tag #:key (verbose? #f))
   "Return a file-like object that, once lowered, will evaluate to a program able
 to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index bac1f47bd34..1fcf6be7ace 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 20:39:19 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 15:39:19 2025
Received: from localhost ([127.0.0.1]:46579 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thE5H-0006xa-9x
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:19 -0500
Received: from confino.investici.org ([93.190.126.19]:27355)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thE5F-0006xA-4O
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:17 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739133556;
 bh=cx52ch+QeD6GNDq7ORhibtW/Xhi7HnMWUEySPhIXRaA=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=G8I14Agm19qaQZ3LTQ9xzVJ/sY0AFC/klqETL9ciktAOsURyIwNSxGD+sIi6408jc
 0nz6KM3OMWYVHAAWeXJeDY3lH9hh1ezbNFHNiFcb4v0GabXsOodmkk71ip0cB5mWkG
 EtE4c5g2FHmPRCEcNwHhdtm5FZILEqtay6oNfl7Y=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yrfh41Y7Gz112l;
 Sun,  9 Feb 2025 20:39:16 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yrfh36l0Wz112H; Sun,  9 Feb 2025 20:39:15 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v3 3/4] services: Add oci-service-type.
Date: Sun,  9 Feb 2025 21:39:07 +0100
Message-ID: <5fa06184f4690fd44c553972dad855feaf7491f5.1739133548.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  299 ++++++++--
 gnu/services/containers.scm | 1086 +++++++++++++++++++++++++++++++----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++++++++-
 4 files changed, 2225 insertions(+), 196 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index bb5f29277fb..8f66b35297b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -41778,59 +41778,159 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (default: @code{"oci-container"}) (type: string)
+The user name under whose authority OCI commands will be run.
+
+@item @code{group} (default: @code{"docker"}) (type: string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -41850,16 +41950,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -41867,22 +41967,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -41910,7 +42012,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -41921,10 +42023,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -41935,25 +42038,95 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly
+passed to the runtime invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
 A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+passed to the runtime invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 50bf6c05549..12c509c07ab 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,72 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-fields
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-networks-shepherd-name
+            oci-volumes-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -294,9 +359,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (maybe-value-set? maybe-group)
+      maybe-group
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -345,6 +443,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -372,6 +475,7 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
+  "Return a string OCI image reference representing IMAGE."
   (if (string? image)
       image
       (string-append (oci-image-repository image)
@@ -390,7 +494,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -436,10 +552,12 @@ (define-configuration/no-serialization oci-image
 (define-configuration/no-serialization oci-container-configuration
   (user
    (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   "The user name under whose authority OCI commands will be run.")
   (group
    (string "docker")
-   "The group under whose authority docker commands will be run.")
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -449,9 +567,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -475,15 +593,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -512,7 +631,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -524,9 +643,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -539,63 +659,326 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invokation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.")
+  (runtime-extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (maybe-value-set? runtime-cli)
+               runtime-cli
+               path)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invokation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define* (get-keyword-value args keyword #:key (default #f))
   (let ((kv (memq keyword args)))
@@ -604,6 +987,7 @@ (define* (get-keyword-value args keyword #:key (default #f))
         default)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -613,6 +997,7 @@ (define (lower-operating-system os target system)
     (return tarball)))
 
 (define (lower-manifest name image target system)
+  "Lower IMAGE, a manifest record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define options (oci-image-pack-options image))
   (define image-reference
@@ -645,6 +1030,7 @@ (define (lower-manifest name image target system)
     (return tarball)))
 
 (define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define image-target (oci-image-target image))
   (define image-system (oci-image-system image))
@@ -675,9 +1061,10 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define* (oci-image-loader runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
       (program-file (format #f "~a-image-loader" name)
        #~(begin
@@ -686,102 +1073,561 @@ (define (%oci-image-loader name image tag)
                         (ice-9 rdelim))
 
            (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+           (when #$verbose?
+             (format #t "Running ~a~%" load-command))
            (define line
              (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
+              (open-input-pipe load-command)))
 
            (unless (or (eof-object? line)
                        (string-null? line))
              (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
+             (let* ((repository&tag
+                     (string-drop line
+                                  (string-length
+                                   "Loaded image: ")))
+                    (tag-command
+                     (list #$runtime-cli "tag" repository&tag #$tag)))
+
+               (when #$verbose?
+                 (format #t "Running~{ ~a~}~%" tag-command))
 
-               (invoke #$docker "tag" repository&tag #$tag)
+               (apply invoke tag-command)
                (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
 
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+(define (oci-container-run-invokation runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invokation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run"
+    "--rm" "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp `(,(first invokation) ,@invokation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (user (or user (oci-container-configuration-user config)))
+         (group (if (and group (maybe-value-set? group))
+                    group
+                    (oci-container-configuration-group config)))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          (oci-container-run-invokation
+           runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments)))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
                        #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
                            (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
+                            (list
+                             #$(oci-container-entrypoint
+                                runtime-cli name image image-reference
+                                invokation #:verbose? verbose?))
+                            #$@(if user (list #:user user) '())
+                            #$@(if group (list #:group group) '())
                             #$@(if (maybe-value-set? log-file)
                                    (list #:log-file log-file)
                                    '())
+                            #$@(if (eq? runtime 'podman)
+                                   (list #:directory
+                                         #~(passwd:dir (getpwnam #$user)))
+                                   '())
                             #:environment-variables
-                            (list #$@host-environment))))
+                            (append
+                             (list #$@host-environment)
+                             (list #$@runtime-environment)))))
                       (stop
                        #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                           (invoke #$runtime-cli "rm" "-f" #$name)))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invokation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 #~(lambda _
+                                     (invoke #$runtime-cli "pull" #$image)))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invokation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define (read-lines file-or-port)
+         (define (loop-lines port)
+           (let loop ((lines '()))
+             (match (read-line port)
+               ((? eof-object?)
+                (reverse lines))
+               (line
+                (loop (cons line lines))))))
+
+         (if (port? file-or-port)
+             (loop-lines file-or-port)
+             (call-with-input-file file-or-port
+               loop-lines)))
+
+       (define (object-exists? name)
+         #$(if (eq? runtime 'podman)
+               #~(let ((command
+                        (list #$runtime-cli
+                              #$object "exists" name)))
+                   (when #$verbose?
+                     (format #t "Running~{ ~a~}~%" command))
+                   (define exit-code (status:exit-val (apply system* command)))
+                   (when #$verbose?
+                     (format #t "Exit code: ~a~%" exit-code))
+                   (equal? EXIT_SUCCESS exit-code))
+               #~(let ((command
+                        (string-append #$runtime-cli
+                                       " " #$object " ls --format "
+                                       "\"{{.Name}}\"")))
+                   (when #$verbose?
+                     (format #t "Running ~a~%" command))
+                   (member name (read-lines (open-input-pipe command))))))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  (let* ((networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol
+                (oci-networks-shepherd-name runtime)))
+              '()))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list
+               (string->symbol
+                (oci-volumes-shepherd-name runtime)))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           (if (string? networks-name)
+               networks-name
+               (oci-networks-shepherd-name runtime))
+           networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           (if (string? volumes-name)
+               volumes-name
+               (oci-volumes-shepherd-name runtime))
+           volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (runtime-cli
+          (oci-runtime-system-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                  #:user user
+                                  #:group
+                                  (oci-runtime-system-group runtime user group)
+                                  #:verbose? verbose?
+                                  #:runtime-extra-arguments
+                                  runtime-extra-arguments
+                                  #:runtime-environment
+                                  (oci-runtime-system-environment runtime user)
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime)
+                                  #:networks-requirement '(networking))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range) (not (string=? (subid-range-name range) user)))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subgids)
+          subgids
+          (subid-range (name user)))
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subuids)
+          subuids
+          (subid-range (name user)))
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-network-configuration-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volume-configuration-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  (list bash-minimal
+        (cond
+         ((maybe-value-set? runtime-cli)
+          runtime-cli)
+         ((eq? 'podman runtime)
+          podman)
+         (else
+          docker-cli))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend
+                 (lambda (config extension)
+                   (oci-configuration
+                    (inherit config)
+                    (containers
+                     (oci-objects-merge-lst
+                      (oci-configuration-containers config)
+                      (oci-extension-containers extension)
+                      "container"
+                      (lambda (oci-config)
+                        (define runtime
+                          (oci-configuration-runtime config))
+                        (oci-container-shepherd-name runtime oci-config))))
+                    (networks (oci-objects-merge-lst
+                               (oci-configuration-networks config)
+                               (oci-extension-networks extension)
+                               "network"
+                               oci-network-configuration-name))
+                    (volumes (oci-objects-merge-lst
+                              (oci-configuration-volumes config)
+                              (oci-extension-volumes extension)
+                              "volume"
+                              oci-volume-configuration-name)))))
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 69ff9f1d9e4..98ee175873d 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -295,17 +300,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..bac1f47bd34 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                 #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 20:39:19 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 15:39:19 2025
Received: from localhost ([127.0.0.1]:46577 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thE5G-0006xW-PS
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:19 -0500
Received: from confino.investici.org ([93.190.126.19]:25459)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thE5E-0006x7-MF
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:39:17 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739133555;
 bh=IdX9voWndJaRYrFOGe4a/X2LYvkwV0vTWt7K16N+Iog=;
 h=From:To:Cc:Subject:Date:From;
 b=OBJmgYJnfwBQc7cX3iPMCaQaXiPgJbLbJnXXZHrwbpf3oXUT7voV6DnB7/rPtfIqI
 vg3cDDA2ZtzVVKdhVWNvGJIPYd246cKG3orv1N9zIGQ82Ranig2rY8Q9rbcf42EH57
 lw/c2M7hRTIl4LRSbeVdHFt2YQqD87FLVy+/t1BI=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4Yrfh31X9rz112M;
 Sun,  9 Feb 2025 20:39:15 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4Yrfh30Plsz112H; Sun,  9 Feb 2025 20:39:15 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v3 1/4] services: rootless-podman: Use login shell.
Date: Sun,  9 Feb 2025 21:39:05 +0100
Message-ID: <89778533f32ad1388f03414e884fff10f74ef379.1739133548.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 19d35ccbcb6..dc66ac4f967 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 5a897c5c95a81278b044c18d962d3bd83131ba06
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 20:38:55 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 15:38:55 2025
Received: from localhost ([127.0.0.1]:46571 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thE4t-0006wD-Cf
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:38:55 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:32661)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thE4q-0006vx-Dh
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 15:38:53 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739133528;
 bh=HYtwwBuGG4jF0I3GDdJnb1K+l+dEy4xSfBm1X561Jto=;
 h=Date:Subject:From:To:References:In-Reply-To:From;
 b=YmyQ2804d5IMCGyG8BQF2OaWM8GoHlxT8E/hHkO8O8bKV25vPtm4Tbs3xTK2E9Ksg
 qnX1+kNsGu3gztf7zd4Q77kKgzbk1oMd3NrZ3dXsJgBTzsT2stZXuQ2mqTN8m27b+R
 7Lu2KeLSJY89pVRkGr7jJ3ihfmnt2C1EOJMpzPi4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrfgX3QgQz112M
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Feb 2025 20:38:48 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrfgX2tMCz112H
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Feb 2025 20:38:48 +0000 (UTC)
Message-ID: <e467b1c6-a54b-400b-97a6-1a7b4082686b@HIDDEN>
Date: Sun, 9 Feb 2025 21:38:47 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
Subject: Re: OCI provisioning service
From: paul <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
References: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
Content-Language: en-US
In-Reply-To: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

I'm sending a v3 fixing a bug in the merge algorithm for volumes and 
networks.

On 2/9/25 20:14, paul wrote:
> Hi,
>
> I'm about to send a v2. v2 compared to the first revision features:
>
>
> - it actually compiles all the times :) (rev 1 referenced oci-image 
> too early for it to be working and generated a compile time error, if 
> you recompiled it sometimes went away so I thought it was a problem of 
> my setup. CI caught this)
> - it allows more values to be overridden by eventual users of the 
> Scheme API
> - it allows passing extra arguments directly after each podman or 
> docker invokation, allowing for example for overriding podman --root 
> and similar options.
>
> All of these tests should pass:
>
> guix shell -D guix -CPW -- make check-system TESTS="oci-container 
> oci-service-rootless-podman docker docker-system rootless-podman 
> oci-service-docker"
>
>
> Thank you for your work,
>
> giacomo
>




Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 19:15:50 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 14:15:50 2025
Received: from localhost ([127.0.0.1]:46334 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thCmS-0002x8-8Z
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:50 -0500
Received: from confino.investici.org ([93.190.126.19]:39077)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thCmI-0002w9-UR
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:42 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739128537;
 bh=C6uHhY9S9WpgPkIaaAgAOz8vooJK2RfngRRsEihvD9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=l9c9NNJ0ugtilFgLhw/3XTinhmOrUk/BLtQku0AVdMdbtVE2jld8Q0q/UcfuXwppS
 cLgtUptwqDzvkDtaYtEzzu8ts2o28QVy0uX0whuy4A3FzcAc8LOn6jDzrzdR7m5oga
 wrN8cIA8R4h7cax6g4YUSgeAIozpoBaH1Oj0BATA=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrcqY2JbCz11Df;
 Sun,  9 Feb 2025 19:15:37 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrcqY13xKz11F7; Sun,  9 Feb 2025 19:15:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v2 2/4] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Sun,  9 Feb 2025 20:15:02 +0100
Message-ID: <92f19ba6c0842885735dfce653eef535360085f4.1739128504.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index dc66ac4f967..50bf6c05549 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -188,7 +237,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -242,3 +291,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 3af0f792701..69ff9f1d9e4 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -307,495 +295,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 19:15:44 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 14:15:44 2025
Received: from localhost ([127.0.0.1]:46332 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thCmN-0002wm-Al
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:44 -0500
Received: from confino.investici.org ([93.190.126.19]:47181)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thCmJ-0002wC-4b
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:40 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739128538;
 bh=y/zqLPAY9rcNRl2CpnNFoTia9j649oQq7kUfZKh6uC4=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=lMVIjxQyUneR0+QXensJvtvzAHRLbWeh97yIfbhPN4xTav5HD/aL0DOwihmEW1gCi
 fp6vAC/wp/EmNKnLj+TprQs2q3Tr2sJ2Cc17r66y5qooi0vxIl2uJBW1HosPkjsGsu
 /rjHTH78XROZtNYbdjxhqxUkHu3Sis/Fl6dEP4x4=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrcqZ0vPnz11FM;
 Sun,  9 Feb 2025 19:15:38 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrcqY710Tz11F7; Sun,  9 Feb 2025 19:15:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v2 4/4] tests: Use lower-oci-image-state in container tests.
Date: Sun,  9 Feb 2025 20:15:04 +0100
Message-ID: <10254ceb4ef758730d46c5c30798833f9f592833.1739128504.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image-state.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |  35 ++++++++-----
 gnu/tests/containers.scm    |  80 ++++++++++++++---------------
 gnu/tests/docker.scm        | 100 ++++++++++++++++++++----------------
 3 files changed, 114 insertions(+), 101 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 5c50c99eaf6..2808afe7f08 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image-state
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
@@ -996,12 +998,9 @@ (define (lower-operating-system os target system)
          #:target target)))
     (return tarball)))
 
-(define (lower-manifest name image target system)
-  "Lower IMAGE, a manifest record, into a tarball containing an OCI image."
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
+(define (lower-manifest name value options image-reference
+                        target system grafts?)
+  "Lower VALUE, a manifest record, into a tarball containing an OCI image."
   (define image-tag
     (let* ((extra-options
             (get-keyword-value options #:extra-options))
@@ -1013,8 +1012,7 @@ (define (lower-manifest name image target system)
           `(#:extra-options (#:image-tag ,image-reference)))))
 
   (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
+      ((_ (set-grafting grafts?))
        (guile (set-guile-for-build (default-guile)))
        (profile
         (profile-derivation value
@@ -1029,11 +1027,8 @@ (define (lower-manifest name image target system)
                          #:localstatedir? #t))))
     (return tarball)))
 
-(define (lower-oci-image name image)
-  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
+(define (lower-oci-image-state name value options reference
+                               image-target image-system grafts?)
   (define target
     (if (maybe-value-set? image-target)
         image-target
@@ -1046,7 +1041,8 @@ (define (lower-oci-image name image)
    (run-with-store store
      (match value
        ((? manifest? value)
-        (lower-manifest name image target system))
+        (lower-manifest name value options reference
+                        target system grafts?))
        ((? operating-system? value)
         (lower-operating-system value target system))
        ((or (? gexp? value)
@@ -1061,6 +1057,17 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
+(define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
+  (lower-oci-image-state
+   name
+   (oci-image-value image)
+   (oci-image-pack-options image)
+   (oci-image-reference image)
+   (oci-image-target image)
+   (oci-image-system image)
+   (oci-image-grafts? image)))
+
 (define* (oci-image-loader runtime-cli name image tag #:key (verbose? #f))
   "Return a file-like object that, once lowered, will evaluate to a program able
 to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index bac1f47bd34..1fcf6be7ace 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %oci-rootless-podman-os
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..07edd9d5341 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image-state
+   "guile-guest"
+   (packages->manifest
+    (list
+     guile-3.0 guile-json-3
+     (package
+       (name "guest-script")
+       (version "0")
+       (source #f)
+       (build-system trivial-build-system)
+       (arguments `(#:guile ,guile-3.0
+                    #:builder
+                    (let ((out (assoc-ref %outputs "out")))
+                      (mkdir out)
+                      (call-with-output-file (string-append out "/a.scm")
+                        (lambda (port)
+                          (display "(display \"hello world\n\")" port)))
+                      #t)))
+       (synopsis "Display hello world using Guile")
+       (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+       (home-page #f)
+       (license license:public-domain))))
+   '(#:entry-point "bin/guile"
+     #:localstatedir? #t
+     #:extra-options (#:image-tag "guile-guest")
+     #:symlinks (("/bin/Guile" -> "bin/guile")
+                 ("aa.scm" -> "a.scm")))
+   "guile-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,22 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image-state
+   "guix-system-guest"
+   (operating-system
+     (inherit (simple-operating-system))
+     ;; Use locales for a single libc to
+     ;; reduce space requirements.
+     (locale-libcs (list glibc)))
+   '()
+   "guix-system-guest"
+   (%current-target-system)
+   (%current-system)
+   #f))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +349,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 19:15:43 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 14:15:43 2025
Received: from localhost ([127.0.0.1]:46331 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thCmN-0002wk-5f
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:43 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:28977)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thCmI-0002wB-Uq
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:39 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739128537;
 bh=yzEAg3727KejHWShjEPKh27mqDTLvagvfAZQ5/oMQRM=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=HEhnPfHPfHhjVMqObh2JTwscsKI1bPBBSh7j6qC6FRBJDAK8KLkYb6JPhvosvton5
 teTOlkee9SDj2TRNbqvZj2k/zzPWEq42f1zoze3/clNXnjxLC6vuuq08iqA0Io6evM
 EeTmFACn9/2AejKkhD9PHh47ElDlRxrtrD9VhgEo=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrcqY5Y7wz11FL;
 Sun,  9 Feb 2025 19:15:37 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrcqY4D8qz11F7; Sun,  9 Feb 2025 19:15:37 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v2 3/4] services: Add oci-service-type.
Date: Sun,  9 Feb 2025 20:15:03 +0100
Message-ID: <c94fe584d2ba0071f64e456e708c1447ab094cd7.1739128504.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  299 ++++++++--
 gnu/services/containers.scm | 1086 +++++++++++++++++++++++++++++++----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  999 +++++++++++++++++++++++++++++++-
 4 files changed, 2225 insertions(+), 196 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index bb5f29277fb..8f66b35297b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -41778,59 +41778,159 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.
+
+@item @code{runtime-extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.
+
+@item @code{user} (default: @code{"oci-container"}) (type: string)
+The user name under whose authority OCI commands will be run.
+
+@item @code{group} (default: @code{"docker"}) (type: string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -41850,16 +41950,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -41867,22 +41967,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -41910,7 +42012,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -41921,10 +42023,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -41935,25 +42038,95 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly
+passed to the runtime invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
 A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+passed to the runtime invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 50bf6c05549..5c50c99eaf6 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,72 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-environment
+            oci-runtime-system-extra-arguments
+            oci-runtime-system-group
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-fields
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-runtime-extra-arguments
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-networks-shepherd-name
+            oci-volumes-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -294,9 +359,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs user-processes)
+      '(dockerd user-processes)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (maybe-value-set? maybe-group)
+      maybe-group
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -345,6 +443,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -372,6 +475,7 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
+  "Return a string OCI image reference representing IMAGE."
   (if (string? image)
       image
       (string-append (oci-image-repository image)
@@ -390,7 +494,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -436,10 +552,12 @@ (define-configuration/no-serialization oci-image
 (define-configuration/no-serialization oci-container-configuration
   (user
    (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   "The user name under whose authority OCI commands will be run.")
   (group
    (string "docker")
-   "The group under whose authority docker commands will be run.")
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -449,9 +567,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -475,15 +593,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -512,7 +631,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -524,9 +643,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -539,63 +659,326 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invokation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.")
+  (runtime-extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be placed
+after each @command{docker} or @command{podman} invokation.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-system-environment runtime user)
+  (if (eq? runtime 'podman)
+      (list
+       #~(string-append
+          "HOME=" (passwd:dir (getpwnam #$user))))
+      #~()))
+
+(define (oci-runtime-system-group runtime user group)
+  (if (eq? runtime 'podman)
+      #~(group:name
+         (getgrgid
+          (passwd:gid
+           (getpwnam #$user))))
+      group))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         #$(if (maybe-value-set? runtime-cli)
+               runtime-cli
+               path)
+         #$(if (eq? 'podman runtime)
+               "/bin/podman"
+               "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invokation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define* (get-keyword-value args keyword #:key (default #f))
   (let ((kv (memq keyword args)))
@@ -604,6 +987,7 @@ (define* (get-keyword-value args keyword #:key (default #f))
         default)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -613,6 +997,7 @@ (define (lower-operating-system os target system)
     (return tarball)))
 
 (define (lower-manifest name image target system)
+  "Lower IMAGE, a manifest record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define options (oci-image-pack-options image))
   (define image-reference
@@ -645,6 +1030,7 @@ (define (lower-manifest name image target system)
     (return tarball)))
 
 (define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define image-target (oci-image-target image))
   (define image-system (oci-image-system image))
@@ -675,9 +1061,10 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define* (oci-image-loader runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
       (program-file (format #f "~a-image-loader" name)
        #~(begin
@@ -686,102 +1073,561 @@ (define (%oci-image-loader name image tag)
                         (ice-9 rdelim))
 
            (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+           (when #$verbose?
+             (format #t "Running ~a~%" load-command))
            (define line
              (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
+              (open-input-pipe load-command)))
 
            (unless (or (eof-object? line)
                        (string-null? line))
              (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
+             (let* ((repository&tag
+                     (string-drop line
+                                  (string-length
+                                   "Loaded image: ")))
+                    (tag-command
+                     (list #$runtime-cli "tag" repository&tag #$tag)))
+
+               (when #$verbose?
+                 (format #t "Running~{ ~a~}~%" tag-command))
 
-               (invoke #$docker "tag" repository&tag #$tag)
+               (apply invoke tag-command)
                (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
 
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+(define (oci-container-run-invokation runtime-cli name command image-reference
+                                      options runtime-extra-arguments run-extra-arguments)
+  "Return a list representing the OCI runtime
+invokation for running containers."
+  ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+  `(,runtime-cli ,@runtime-extra-arguments "run"
+    "--rm" "--name" ,name
+    ,@options ,@run-extra-arguments
+    ,image-reference ,@command))
+
+(define* (oci-container-entrypoint runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format)
+                    (srfi srfi-1))
+       (when #$verbose?
+         (format #t "Running in verbose mode...~%"))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp `(,(first invokation) ,@invokation)))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (runtime-environment #~())
+                                         (runtime-extra-arguments '())
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (user (or user (oci-container-configuration-user config)))
+         (group (if (and group (maybe-value-set? group))
+                    group
+                    (oci-container-configuration-group config)))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          (oci-container-run-invokation
+           runtime-cli name command image-reference
+           options runtime-extra-arguments extra-arguments)))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
                        #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
                            (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
+                            (list
+                             #$(oci-container-entrypoint
+                                runtime-cli name image image-reference
+                                invokation #:verbose? verbose?))
+                            #$@(if user (list #:user user) '())
+                            #$@(if group (list #:group group) '())
                             #$@(if (maybe-value-set? log-file)
                                    (list #:log-file log-file)
                                    '())
+                            #$@(if (eq? runtime 'podman)
+                                   (list #:directory
+                                         #~(passwd:dir (getpwnam #$user)))
+                                   '())
                             #:environment-variables
-                            (list #$@host-environment))))
+                            (append
+                             (list #$@host-environment)
+                             (list #$@runtime-environment)))))
                       (stop
                        #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                           (invoke #$runtime-cli "rm" "-f" #$name)))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (list #$@invokation) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 #~(lambda _
+                                     (invoke #$runtime-cli "pull" #$image)))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      runtime-extra-arguments
+                                      create-extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invokation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$@runtime-extra-arguments #$object "create"
+          #$@options #$@create-extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define (read-lines file-or-port)
+         (define (loop-lines port)
+           (let loop ((lines '()))
+             (match (read-line port)
+               ((? eof-object?)
+                (reverse lines))
+               (line
+                (loop (cons line lines))))))
+
+         (if (port? file-or-port)
+             (loop-lines file-or-port)
+             (call-with-input-file file-or-port
+               loop-lines)))
+
+       (define (object-exists? name)
+         #$(if (eq? runtime 'podman)
+               #~(let ((command
+                        (list #$runtime-cli
+                              #$object "exists" name)))
+                   (when #$verbose?
+                     (format #t "Running~{ ~a~}~%" command))
+                   (define exit-code (status:exit-val (apply system* command)))
+                   (when #$verbose?
+                     (format #t "Exit code: ~a~%" exit-code))
+                   (equal? EXIT_SUCCESS exit-code))
+               #~(let ((command
+                        (string-append #$runtime-cli
+                                       " " #$object " ls --format "
+                                       "\"{{.Name}}\"")))
+                   (when #$verbose?
+                     (format #t "Running ~a~%" command))
+                   (member name (read-lines (open-input-pipe command))))))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (runtime-environment #~())
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement requirement)
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #$@(if user (list #:user user) '())
+                          #$@(if group (list #:group group) '())
+                          #:environment-variables
+                          (list #$@runtime-environment))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key (user #f) (group #f) (verbose? #f)
+                                        (runtime-extra-arguments '())
+                                        (runtime-environment #~())
+                                        (runtime-requirement '()))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             runtime-extra-arguments
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             runtime-extra-arguments
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:runtime-environment runtime-environment
+     #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-extra-arguments '())
+                                       (runtime-environment #~())
+                                       (runtime-requirement '())
+                                       (containers-requirement '())
+                                       (networks-requirement '())
+                                       (volumes-requirement '()))
+  (let* ((networks?
+          (> (length networks) 0))
+         (networks-service
+          (if networks?
+              (list
+               (string->symbol
+                (oci-networks-shepherd-name runtime)))
+              '()))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-service
+          (if volumes?
+              (list
+               (string->symbol
+                (oci-volumes-shepherd-name runtime)))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:runtime-environment runtime-environment
+         #:runtime-extra-arguments runtime-extra-arguments
+         #:oci-requirement
+         (append containers-requirement
+                 runtime-requirement
+                 networks-service
+                 volumes-service)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           (if (string? networks-name)
+               networks-name
+               (oci-networks-shepherd-name runtime))
+           networks
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append networks-requirement
+                                         runtime-requirement)
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service
+           runtime runtime-cli
+           (if (string? volumes-name)
+               volumes-name
+               (oci-volumes-shepherd-name runtime))
+           volumes
+           #:user user #:group group
+           #:runtime-extra-arguments runtime-extra-arguments
+           #:runtime-environment runtime-environment
+           #:runtime-requirement (append runtime-requirement
+                                         volumes-requirement)
+           #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (runtime-cli
+          (oci-runtime-system-cli config))
+         (runtime-extra-arguments
+          (oci-configuration-runtime-extra-arguments config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                  #:user user
+                                  #:group
+                                  (oci-runtime-system-group runtime user group)
+                                  #:verbose? verbose?
+                                  #:runtime-extra-arguments
+                                  runtime-extra-arguments
+                                  #:runtime-environment
+                                  (oci-runtime-system-environment runtime user)
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime)
+                                  #:networks-requirement '(networking))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range) (not (string=? (subid-range-name range) user)))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subgids)
+          subgids
+          (subid-range (name user)))
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subuids)
+          subuids
+          (subid-range (name user)))
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-networks-shepherd-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volumes-shepherd-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  (list bash-minimal
+        (cond
+         ((maybe-value-set? runtime-cli)
+          runtime-cli)
+         ((eq? 'podman runtime)
+          podman)
+         (else
+          docker-cli))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend
+                 (lambda (config extension)
+                   (oci-configuration
+                    (inherit config)
+                    (containers
+                     (oci-objects-merge-lst
+                      (oci-configuration-containers config)
+                      (oci-extension-containers extension)
+                      "container"
+                      (lambda (oci-config)
+                        (define runtime
+                          (oci-configuration-runtime config))
+                        (oci-container-shepherd-name runtime oci-config))))
+                    (networks (oci-objects-merge-lst
+                               (oci-configuration-networks config)
+                               (oci-extension-networks extension)
+                               "network"
+                               oci-networks-shepherd-name))
+                    (volumes (oci-objects-merge-lst
+                              (oci-configuration-volumes config)
+                              (oci-extension-volumes extension)
+                              "volume"
+                              oci-volumes-shepherd-name)))))
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 69ff9f1d9e4..98ee175873d 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -295,17 +300,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..bac1f47bd34 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,995 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                 #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   (oci-extension
+                    (networks
+                     (list (oci-network-configuration (name "my-network"))))
+                    (volumes
+                     (list (oci-volume-configuration (name "my-volume"))))
+                    (containers
+                     (list
+                      (oci-container-configuration
+                       (provision "first")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+                       (host-environment
+                        '(("VARIABLE" . "value")))
+                       (volumes
+                        '(("my-volume" . "/my-volume")))
+                       (extra-arguments
+                        '("--env" "VARIABLE")))
+                      (oci-container-configuration
+                       (provision "second")
+                       (image
+                        (oci-image
+                         (repository "guile")
+                         (value
+                          (specifications->manifest '("guile")))
+                         (pack-options
+                          '(#:symlinks (("/bin" -> "bin"))))))
+                       (entrypoint "/bin/guile")
+                       (network "my-network")
+                       (command
+                        '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+                       (volumes
+                        '(("my-volume" . "/my-volume")
+                          ("/shared.txt" . "/shared.txt:ro"))))))))))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 19:15:41 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 14:15:41 2025
Received: from localhost ([127.0.0.1]:46329 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thCmJ-0002wL-Ho
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:41 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:45329)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thCmH-0002w6-T3
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:15:38 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739128536;
 bh=IdX9voWndJaRYrFOGe4a/X2LYvkwV0vTWt7K16N+Iog=;
 h=From:To:Cc:Subject:Date:From;
 b=RxFnfHFOQJAVXzlot9YMq9wkovxQshhnbb8Wcd2YjbkVYPELa0MldQJLF8YXuN+3E
 gxgqFxqVfIYm5kh7GAjVNFZ2wmNcM+IOlEQ46tz9pcnxCFTEOP8AFfPnGYVFdMzQ6+
 7RMHgESzyaBBw2xgswtIhFkhWXGnaz4rp3b/I6nk=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrcqX6bJBz11Cp;
 Sun,  9 Feb 2025 19:15:36 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrcqX5YJHz11F7; Sun,  9 Feb 2025 19:15:36 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH v2 1/4] services: rootless-podman: Use login shell.
Date: Sun,  9 Feb 2025 20:15:01 +0100
Message-ID: <89778533f32ad1388f03414e884fff10f74ef379.1739128504.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 19d35ccbcb6..dc66ac4f967 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 5a897c5c95a81278b044c18d962d3bd83131ba06
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 9 Feb 2025 19:14:41 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 14:14:41 2025
Received: from localhost ([127.0.0.1]:46320 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thClM-0002oN-Rs
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:14:41 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:47541)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1thClJ-0002o5-SP
 for 76081 <at> debbugs.gnu.org; Sun, 09 Feb 2025 14:14:39 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1739128474;
 bh=obw0w5hG5W64ht/JHyDk9qs/lqcbkWyMrEDV3XOZL/E=;
 h=Date:To:From:Subject:From;
 b=KUbt4bJFDGi8YHDtMEKpc4lo3QfNu87PqnHS3Ab4DnBhe3bXZ96LFCShSS/Ek7dwn
 wRLe2EggaMDHU8n6S28XhaHivxiJ5yraOgu7dXK+pXihtRCqgrBBQs1shPo8MlKS6p
 bJj20VCTjztzf4N77w9pqEnKl9jkqPCZ/IK8UABk=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YrcpL0l6Zz11Df
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Feb 2025 19:14:34 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YrcpL03Qcz11Cp
 for <76081 <at> debbugs.gnu.org>; Sun,  9 Feb 2025 19:14:33 +0000 (UTC)
Message-ID: <c685875a-0b74-4ada-9a04-c387b8db646e@HIDDEN>
Date: Sun, 9 Feb 2025 20:14:33 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
To: 76081 <at> debbugs.gnu.org
Content-Language: en-US
From: paul <goodoldpaul@HIDDEN>
Subject: Re: OCI provisioning service
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi,

I'm about to send a v2. v2 compared to the first revision features:


- it actually compiles all the times :) (rev 1 referenced oci-image too 
early for it to be working and generated a compile time error, if you 
recompiled it sometimes went away so I thought it was a problem of my 
setup. CI caught this)
- it allows more values to be overridden by eventual users of the Scheme API
- it allows passing extra arguments directly after each podman or docker 
invokation, allowing for example for overriding podman --root and 
similar options.

All of these tests should pass:

guix shell -D guix -CPW -- make check-system TESTS="oci-container 
oci-service-rootless-podman docker docker-system rootless-podman 
oci-service-docker"


Thank you for your work,

giacomo





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 Feb 2025 22:03:23 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 05 17:03:23 2025
Received: from localhost ([127.0.0.1]:53030 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfnUO-0003ri-K3
	for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:23 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:25221)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnUG-0003qp-Vp
 for 76081 <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:16 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1738792990;
 bh=C6uHhY9S9WpgPkIaaAgAOz8vooJK2RfngRRsEihvD9o=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=mCb7fsmCuTThphUNjdAVAKxzVf6SW7ThY1U6WGIDC+B632QS3NfNrnAvL03sHt+Sg
 CWBMPQrO7OVlwLm20jId/OLeu1qEqytsDxN2vwZJsKSrP/EAKLmDPo4b0bZfIswKup
 /YwTBRWmmzJophH6632Bd6nKqj9Fka4iDfo5KQJQ=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YpDkk1HPPz112w;
 Wed,  5 Feb 2025 22:03:10 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YpDkj751mz1156; Wed,  5 Feb 2025 22:03:09 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH 2/4] services: oci-container-configuration: Move to (gnu
 services containers).
Date: Wed,  5 Feb 2025 23:02:29 +0100
Message-ID: <92f19ba6c0842885735dfce653eef535360085f4.1738792951.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch moves the oci-container-configuration and related
configuration records to (gnu services containers).
Public symbols are still exported for backwards
compatibility but since the oci-container-service-type will be
deprecated in favor of the more general oci-service-type, everything is
moved outside of the docker related module.

* gnu/services/docker.scm: Move everything related to oci-container-configuration
to...
* gnu/services/containers.scm: ...here.scm.
* gnu/tests/docker.scm: Simplify %test-oci-container test case.

Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7
---
 gnu/services/containers.scm | 549 +++++++++++++++++++++++++++++++++-
 gnu/services/docker.scm     | 577 +++---------------------------------
 gnu/tests/docker.scm        |  99 +++----
 3 files changed, 625 insertions(+), 600 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index dc66ac4f967..50bf6c05549 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -17,19 +17,31 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services containers)
+  #:use-module (gnu image)
+  #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
   #:use-module (gnu packages containers)
+  #:use-module (gnu packages docker)
   #:use-module (gnu packages file-systems)
   #:use-module (gnu services)
   #:use-module (gnu services base)
   #:use-module (gnu services configuration)
   #:use-module (gnu services shepherd)
+  #:use-module (gnu system)
   #:use-module (gnu system accounts)
+  #:use-module (gnu system image)
   #:use-module (gnu system shadow)
   #:use-module (gnu system pam)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
+  #:use-module (guix monads)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
+  #:use-module ((guix scripts pack) #:prefix pack:)
+  #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
             rootless-podman-configuration-fields
@@ -48,7 +60,44 @@ (define-module (gnu services containers)
             rootless-podman-shepherd-services
             rootless-podman-service-etc
 
-            rootless-podman-service-type))
+            rootless-podman-service-type
+
+            oci-image
+            oci-image?
+            oci-image-fields
+            oci-image-repository
+            oci-image-tag
+            oci-image-value
+            oci-image-pack-options
+            oci-image-target
+            oci-image-system
+            oci-image-grafts?
+
+            oci-container-configuration
+            oci-container-configuration?
+            oci-container-configuration-fields
+            oci-container-configuration-user
+            oci-container-configuration-group
+            oci-container-configuration-command
+            oci-container-configuration-entrypoint
+            oci-container-configuration-host-environment
+            oci-container-configuration-environment
+            oci-container-configuration-image
+            oci-container-configuration-provision
+            oci-container-configuration-requirement
+            oci-container-configuration-log-file
+            oci-container-configuration-auto-start?
+            oci-container-configuration-respawn?
+            oci-container-configuration-shepherd-actions
+            oci-container-configuration-network
+            oci-container-configuration-ports
+            oci-container-configuration-volumes
+            oci-container-configuration-container-user
+            oci-container-configuration-workdir
+            oci-container-configuration-extra-arguments
+
+            oci-container-shepherd-service
+            %oci-container-accounts))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -188,7 +237,7 @@ (define (rootless-podman-cgroups-limits-service config)
                        rootless-podman-shared-root-fs))
                     (one-shot? #t)
                     (documentation
-                     "Allow setting cgroups limits: cpu, cpuset, memory and
+                     "Allow setting cgroups limits: cpu, cpuset, io, memory and
 pids.")
                     (start
                      #~(make-forkexec-constructor
@@ -242,3 +291,497 @@ (define rootless-podman-service-type
                 (default-value (rootless-podman-configuration))
                 (description
                  "This service configures rootless @code{podman} on the Guix System.")))
+
+
+;;;
+;;; OCI container.
+;;;
+
+(define (oci-sanitize-pair pair delimiter)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (match pair
+    (((? valid? key) . (? valid? value))
+     #~(string-append #$key #$delimiter #$value))
+    (_
+     (raise
+      (formatted-message
+       (G_ "pair members must contain only strings, gexps or file-like objects
+but ~a was found")
+       pair)))))
+
+(define (oci-sanitize-mixed-list name value delimiter)
+  (map
+   (lambda (el)
+     (cond ((string? el) el)
+           ((pair? el) (oci-sanitize-pair el delimiter))
+           (else
+            (raise
+             (formatted-message
+              (G_ "~a members must be either a string or a pair but ~a was
+found!")
+              name el)))))
+   value))
+
+(define (oci-sanitize-host-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "host-environment" value "="))
+
+(define (oci-sanitize-environment value)
+  ;; Expected spec format:
+  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
+  (oci-sanitize-mixed-list "environment" value "="))
+
+(define (oci-sanitize-ports value)
+  ;; Expected spec format:
+  ;; '(("8088" . "80") "2022:22")
+  (oci-sanitize-mixed-list "ports" value ":"))
+
+(define (oci-sanitize-volumes value)
+  ;; Expected spec format:
+  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
+  (oci-sanitize-mixed-list "volumes" value ":"))
+
+(define (oci-sanitize-shepherd-actions value)
+  (map
+   (lambda (el)
+     (if (shepherd-action? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "shepherd-actions may only be shepherd-action records
+but ~a was found") el))))
+   value))
+
+(define (oci-sanitize-extra-arguments value)
+  (define (valid? member)
+    (or (string? member)
+        (gexp? member)
+        (file-like? member)))
+  (map
+   (lambda (el)
+     (if (valid? el)
+         el
+         (raise
+          (formatted-message
+           (G_ "extra arguments may only be strings, gexps or file-like objects
+but ~a was found") el))))
+   value))
+
+(define (oci-image-reference image)
+  (if (string? image)
+      image
+      (string-append (oci-image-repository image)
+                     ":" (oci-image-tag image))))
+
+(define (oci-lowerable-image? image)
+  (or (manifest? image)
+      (operating-system? image)
+      (gexp? image)
+      (file-like? image)))
+
+(define (string-or-oci-image? image)
+  (or (string? image)
+      (oci-image? image)))
+
+(define list-of-symbols?
+  (list-of symbol?))
+
+(define-maybe/no-serialization string)
+
+(define-configuration/no-serialization oci-image
+  (repository
+   (string)
+   "A string like @code{myregistry.local:5000/testing/test-image} that names
+the OCI image.")
+  (tag
+   (string "latest")
+   "A string representing the OCI image tag. Defaults to @code{latest}.")
+  (value
+   (oci-lowerable-image)
+   "A @code{manifest} or @code{operating-system} record that will be lowered
+into an OCI compatible tarball.  Otherwise this field's value can be a gexp
+or a file-like object that evaluates to an OCI compatible tarball.")
+  (pack-options
+   (list '())
+   "An optional set of keyword arguments that will be passed to the
+@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
+to replicate @command{guix pack} behavior:
+
+@lisp
+(oci-image
+  (repository \"guile\")
+  (tag \"3\")
+  (manifest (specifications->manifest '(\"guile\")))
+  (pack-options
+    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
+      #:max-layers 2)))
+@end lisp
+
+If the @code{value} field is an @code{operating-system} record, this field's
+value will be ignored.")
+  (system
+   (maybe-string)
+   "Attempt to build for a given system, e.g. \"i686-linux\"")
+  (target
+   (maybe-string)
+   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
+  (grafts?
+   (boolean #f)
+   "Whether to allow grafting or not in the pack build."))
+
+(define-configuration/no-serialization oci-container-configuration
+  (user
+   (string "oci-container")
+   "The user under whose authority docker commands will be run.")
+  (group
+   (string "docker")
+   "The group under whose authority docker commands will be run.")
+  (command
+   (list-of-strings '())
+   "Overwrite the default command (@code{CMD}) of the image.")
+  (entrypoint
+   (maybe-string)
+   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
+  (host-environment
+   (list '())
+   "Set environment variables in the host environment where @command{docker run}
+is invoked.  This is especially useful to pass secrets from the host to the
+container without having them on the @command{docker run}'s command line: by
+setting the @code{MYSQL_PASSWORD} on the host and by passing
+@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
+possible to securely set values in the container environment.  This field's
+value can be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to @code{make-forkexec-constructor}."
+   (sanitizer oci-sanitize-host-environment))
+  (environment
+   (list '())
+   "Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
+
+@lisp
+(list '(\"LANGUAGE\" . \"eo:ca:eu\")
+      \"JAVA_HOME=/opt/java\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-environment))
+  (image
+   (string-or-oci-image)
+   "The image used to build the container.  It can be a string or an
+@code{oci-image} record.  Strings are resolved by the Docker
+Engine, and follow the usual format
+@code{myregistry.local:5000/testing/test-image:tag}.")
+  (provision
+   (maybe-string)
+   "Set the name of the provisioned Shepherd service.")
+  (requirement
+   (list-of-symbols '())
+   "Set additional Shepherd services dependencies to the provisioned Shepherd
+service.")
+  (log-file
+   (maybe-string)
+   "When @code{log-file} is set, it names the file to which the service’s
+standard output and standard error are redirected.  @code{log-file} is created
+if it does not exist, otherwise it is appended to.")
+  (auto-start?
+   (boolean #t)
+   "Whether this service should be started automatically by the Shepherd.  If it
+is @code{#f} the service has to be started manually with @command{herd start}.")
+  (respawn?
+   (boolean #f)
+   "Whether to restart the service when it stops, for instance when the
+underlying process dies.")
+  (shepherd-actions
+   (list '())
+   "This is a list of @code{shepherd-action} records defining actions supported
+by the service."
+   (sanitizer oci-sanitize-shepherd-actions))
+  (network
+   (maybe-string)
+   "Set a Docker network for the spawned container.")
+  (ports
+   (list '())
+   "Set the port or port ranges to expose from the spawned container.  This can
+be a list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"8080\" . \"80\")
+      \"10443:443\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-ports))
+  (volumes
+   (list '())
+   "Set volume mappings for the spawned container.  This can be a
+list of pairs or strings, even mixed:
+
+@lisp
+(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
+      \"/gnu/store:/gnu/store\")
+@end lisp
+
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the Docker CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
+documentation for semantics."
+   (sanitizer oci-sanitize-volumes))
+  (container-user
+   (maybe-string)
+   "Set the current user inside the spawned container.  You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#user,upstream}
+documentation for semantics.")
+  (workdir
+   (maybe-string)
+   "Set the current working for the spawned Shepherd service.
+You can refer to the
+@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
+documentation for semantics.")
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define oci-container-configuration->options
+  (lambda (config)
+    (let ((entrypoint
+           (oci-container-configuration-entrypoint config))
+          (network
+           (oci-container-configuration-network config))
+          (user
+           (oci-container-configuration-container-user config))
+          (workdir
+           (oci-container-configuration-workdir config)))
+      (apply append
+             (filter (compose not unspecified?)
+                     `(,(if (maybe-value-set? entrypoint)
+                            `("--entrypoint" ,entrypoint)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "--env" spec))
+                         (oci-container-configuration-environment config))
+                       ,(if (maybe-value-set? network)
+                            `("--network" ,network)
+                            '())
+                       ,(if (maybe-value-set? user)
+                            `("--user" ,user)
+                            '())
+                       ,(if (maybe-value-set? workdir)
+                            `("--workdir" ,workdir)
+                            '())
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-p" spec))
+                         (oci-container-configuration-ports config))
+                       ,(append-map
+                         (lambda (spec)
+                           (list "-v" spec))
+                         (oci-container-configuration-volumes config))))))))
+
+(define* (get-keyword-value args keyword #:key (default #f))
+  (let ((kv (memq keyword args)))
+    (if (and kv (>= (length kv) 2))
+        (cadr kv)
+        default)))
+
+(define (lower-operating-system os target system)
+  (mlet* %store-monad
+      ((tarball
+        (lower-object
+         (system-image (os->image os #:type docker-image-type))
+         system
+         #:target target)))
+    (return tarball)))
+
+(define (lower-manifest name image target system)
+  (define value (oci-image-value image))
+  (define options (oci-image-pack-options image))
+  (define image-reference
+    (oci-image-reference image))
+  (define image-tag
+    (let* ((extra-options
+            (get-keyword-value options #:extra-options))
+           (image-tag-option
+            (and extra-options
+                 (get-keyword-value extra-options #:image-tag))))
+      (if image-tag-option
+          '()
+          `(#:extra-options (#:image-tag ,image-reference)))))
+
+  (mlet* %store-monad
+      ((_ (set-grafting
+           (oci-image-grafts? image)))
+       (guile (set-guile-for-build (default-guile)))
+       (profile
+        (profile-derivation value
+                            #:target target
+                            #:system system
+                            #:hooks '()
+                            #:locales? #f))
+       (tarball (apply pack:docker-image
+                       `(,name ,profile
+                         ,@options
+                         ,@image-tag
+                         #:localstatedir? #t))))
+    (return tarball)))
+
+(define (lower-oci-image name image)
+  (define value (oci-image-value image))
+  (define image-target (oci-image-target image))
+  (define image-system (oci-image-system image))
+  (define target
+    (if (maybe-value-set? image-target)
+        image-target
+        (%current-target-system)))
+  (define system
+    (if (maybe-value-set? image-system)
+        image-system
+        (%current-system)))
+  (with-store store
+   (run-with-store store
+     (match value
+       ((? manifest? value)
+        (lower-manifest name image target system))
+       ((? operating-system? value)
+        (lower-operating-system value target system))
+       ((or (? gexp? value)
+            (? file-like? value))
+        value)
+       (_
+        (raise
+         (formatted-message
+          (G_ "oci-image value must contain only manifest,
+operating-system, gexp or file-like records but ~a was found")
+          value))))
+     #:target target
+     #:system system)))
+
+(define (%oci-image-loader name image tag)
+  (let ((docker (file-append docker-cli "/bin/docker"))
+        (tarball (lower-oci-image name image)))
+    (with-imported-modules '((guix build utils))
+      (program-file (format #f "~a-image-loader" name)
+       #~(begin
+           (use-modules (guix build utils)
+                        (ice-9 popen)
+                        (ice-9 rdelim))
+
+           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define line
+             (read-line
+              (open-input-pipe
+               (string-append #$docker " load -i " #$tarball))))
+
+           (unless (or (eof-object? line)
+                       (string-null? line))
+             (format #t "~a~%" line)
+             (let ((repository&tag
+                    (string-drop line
+                                 (string-length
+                                   "Loaded image: "))))
+
+               (invoke #$docker "tag" repository&tag #$tag)
+               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
+
+(define (oci-container-shepherd-service config)
+  (define (guess-name name image)
+    (if (maybe-value-set? name)
+        name
+        (string-append "docker-"
+                       (basename
+                        (if (string? image)
+                            (first (string-split image #\:))
+                            (oci-image-repository image))))))
+
+  (let* ((docker (file-append docker-cli "/bin/docker"))
+         (actions (oci-container-configuration-shepherd-actions config))
+         (auto-start?
+          (oci-container-configuration-auto-start? config))
+         (user (oci-container-configuration-user config))
+         (group (oci-container-configuration-group config))
+         (host-environment
+          (oci-container-configuration-host-environment config))
+         (command (oci-container-configuration-command config))
+         (log-file (oci-container-configuration-log-file config))
+         (provision (oci-container-configuration-provision config))
+         (requirement (oci-container-configuration-requirement config))
+         (respawn?
+          (oci-container-configuration-respawn? config))
+         (image (oci-container-configuration-image config))
+         (image-reference (oci-image-reference image))
+         (options (oci-container-configuration->options config))
+         (name (guess-name provision image))
+         (extra-arguments
+          (oci-container-configuration-extra-arguments config)))
+
+    (shepherd-service (provision `(,(string->symbol name)))
+                      (requirement `(dockerd user-processes ,@requirement))
+                      (respawn? respawn?)
+                      (auto-start? auto-start?)
+                      (documentation
+                       (string-append
+                        "Docker backed Shepherd service for "
+                        (if (oci-image? image) name image) "."))
+                      (start
+                       #~(lambda ()
+                           #$@(if (oci-image? image)
+                                  #~((invoke #$(%oci-image-loader
+                                                name image image-reference)))
+                                  #~())
+                           (fork+exec-command
+                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+                            (list #$docker "run" "--rm" "--name" #$name
+                                  #$@options #$@extra-arguments
+                                  #$image-reference #$@command)
+                            #:user #$user
+                            #:group #$group
+                            #$@(if (maybe-value-set? log-file)
+                                   (list #:log-file log-file)
+                                   '())
+                            #:environment-variables
+                            (list #$@host-environment))))
+                      (stop
+                       #~(lambda _
+                           (invoke #$docker "rm" "-f" #$name)))
+                      (actions
+                       (if (oci-image? image)
+                           '()
+                           (append
+                            (list
+                             (shepherd-action
+                              (name 'pull)
+                              (documentation
+                               (format #f "Pull ~a's image (~a)."
+                                       name image))
+                              (procedure
+                               #~(lambda _
+                                   (invoke #$docker "pull" #$image)))))
+                            actions))))))
+
+(define %oci-container-accounts
+  (list (user-account
+         (name "oci-container")
+         (comment "OCI services account")
+         (group "docker")
+         (system? #t)
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 3af0f792701..69ff9f1d9e4 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@HIDDEN>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@HIDDEN>
 ;;; Copyright © 2021 Brice Waegeneire <brice@HIDDEN>
-;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -23,72 +23,60 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (gnu services docker)
-  #:use-module (gnu image)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
-  #:use-module (gnu services base)
-  #:use-module (gnu services dbus)
+  #:use-module (gnu services containers)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu system)
-  #:use-module (gnu system image)
   #:use-module (gnu system privilege)
   #:use-module (gnu system shadow)
-  #:use-module (gnu packages admin)               ;shadow
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
-  #:use-module (guix records)
-  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
-  #:use-module (guix i18n)
-  #:use-module (guix monads)
-  #:use-module (guix packages)
-  #:use-module (guix profiles)
-  #:use-module ((guix scripts pack) #:prefix pack:)
-  #:use-module (guix store)
+  #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
+  #:re-export (oci-image                          ;for backwards compatibility, until the
+               oci-image?                         ;oci-container-service-type is fully deprecated
+               oci-image-fields
+               oci-image-repository
+               oci-image-tag
+               oci-image-value
+               oci-image-pack-options
+               oci-image-target
+               oci-image-system
+               oci-image-grafts?
+               oci-container-configuration
+               oci-container-configuration?
+               oci-container-configuration-fields
+               oci-container-configuration-user
+               oci-container-configuration-group
+               oci-container-configuration-command
+               oci-container-configuration-entrypoint
+               oci-container-configuration-host-environment
+               oci-container-configuration-environment
+               oci-container-configuration-image
+               oci-container-configuration-provision
+               oci-container-configuration-requirement
+               oci-container-configuration-log-file
+               oci-container-configuration-auto-start?
+               oci-container-configuration-respawn?
+               oci-container-configuration-shepherd-actions
+               oci-container-configuration-network
+               oci-container-configuration-ports
+               oci-container-configuration-volumes
+               oci-container-configuration-container-user
+               oci-container-configuration-workdir
+               oci-container-configuration-extra-arguments
+               oci-container-shepherd-service
+               %oci-container-accounts)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-image
-            oci-image?
-            oci-image-fields
-            oci-image-repository
-            oci-image-tag
-            oci-image-value
-            oci-image-pack-options
-            oci-image-target
-            oci-image-system
-            oci-image-grafts?
-            oci-container-configuration
-            oci-container-configuration?
-            oci-container-configuration-fields
-            oci-container-configuration-user
-            oci-container-configuration-group
-            oci-container-configuration-command
-            oci-container-configuration-entrypoint
-            oci-container-configuration-host-environment
-            oci-container-configuration-environment
-            oci-container-configuration-image
-            oci-container-configuration-provision
-            oci-container-configuration-requirement
-            oci-container-configuration-log-file
-            oci-container-configuration-auto-start?
-            oci-container-configuration-respawn?
-            oci-container-configuration-shepherd-actions
-            oci-container-configuration-network
-            oci-container-configuration-ports
-            oci-container-configuration-volumes
-            oci-container-configuration-container-user
-            oci-container-configuration-workdir
-            oci-container-configuration-extra-arguments
-            oci-container-service-type
-            oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-container-service-type))
 
 (define-maybe file-like)
 
@@ -307,495 +295,6 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (oci-sanitize-pair pair delimiter)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (match pair
-    (((? valid? key) . (? valid? value))
-     #~(string-append #$key #$delimiter #$value))
-    (_
-     (raise
-      (formatted-message
-       (G_ "pair members must contain only strings, gexps or file-like objects
-but ~a was found")
-       pair)))))
-
-(define (oci-sanitize-mixed-list name value delimiter)
-  (map
-   (lambda (el)
-     (cond ((string? el) el)
-           ((pair? el) (oci-sanitize-pair el delimiter))
-           (else
-            (raise
-             (formatted-message
-              (G_ "~a members must be either a string or a pair but ~a was
-found!")
-              name el)))))
-   value))
-
-(define (oci-sanitize-host-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "host-environment" value "="))
-
-(define (oci-sanitize-environment value)
-  ;; Expected spec format:
-  ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
-  (oci-sanitize-mixed-list "environment" value "="))
-
-(define (oci-sanitize-ports value)
-  ;; Expected spec format:
-  ;; '(("8088" . "80") "2022:22")
-  (oci-sanitize-mixed-list "ports" value ":"))
-
-(define (oci-sanitize-volumes value)
-  ;; Expected spec format:
-  ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
-  (oci-sanitize-mixed-list "volumes" value ":"))
-
-(define (oci-sanitize-shepherd-actions value)
-  (map
-   (lambda (el)
-     (if (shepherd-action? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "shepherd-actions may only be shepherd-action records
-but ~a was found") el))))
-   value))
-
-(define (oci-sanitize-extra-arguments value)
-  (define (valid? member)
-    (or (string? member)
-        (gexp? member)
-        (file-like? member)))
-  (map
-   (lambda (el)
-     (if (valid? el)
-         el
-         (raise
-          (formatted-message
-           (G_ "extra arguments may only be strings, gexps or file-like objects
-but ~a was found") el))))
-   value))
-
-(define (oci-image-reference image)
-  (if (string? image)
-      image
-      (string-append (oci-image-repository image)
-                     ":" (oci-image-tag image))))
-
-(define (oci-lowerable-image? image)
-  (or (manifest? image)
-      (operating-system? image)
-      (gexp? image)
-      (file-like? image)))
-
-(define (string-or-oci-image? image)
-  (or (string? image)
-      (oci-image? image)))
-
-(define list-of-symbols?
-  (list-of symbol?))
-
-(define-maybe/no-serialization string)
-
-(define-configuration/no-serialization oci-image
-  (repository
-   (string)
-   "A string like @code{myregistry.local:5000/testing/test-image} that names
-the OCI image.")
-  (tag
-   (string "latest")
-   "A string representing the OCI image tag. Defaults to @code{latest}.")
-  (value
-   (oci-lowerable-image)
-   "A @code{manifest} or @code{operating-system} record that will be lowered
-into an OCI compatible tarball.  Otherwise this field's value can be a gexp
-or a file-like object that evaluates to an OCI compatible tarball.")
-  (pack-options
-   (list '())
-   "An optional set of keyword arguments that will be passed to the
-@code{docker-image} procedure from @code{guix scripts pack}.  They can be used
-to replicate @command{guix pack} behavior:
-
-@lisp
-(oci-image
-  (repository \"guile\")
-  (tag \"3\")
-  (manifest (specifications->manifest '(\"guile\")))
-  (pack-options
-    '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
-      #:max-layers 2)))
-@end lisp
-
-If the @code{value} field is an @code{operating-system} record, this field's
-value will be ignored.")
-  (system
-   (maybe-string)
-   "Attempt to build for a given system, e.g. \"i686-linux\"")
-  (target
-   (maybe-string)
-   "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
-  (grafts?
-   (boolean #f)
-   "Whether to allow grafting or not in the pack build."))
-
-(define-configuration/no-serialization oci-container-configuration
-  (user
-   (string "oci-container")
-   "The user under whose authority docker commands will be run.")
-  (group
-   (string "docker")
-   "The group under whose authority docker commands will be run.")
-  (command
-   (list-of-strings '())
-   "Overwrite the default command (@code{CMD}) of the image.")
-  (entrypoint
-   (maybe-string)
-   "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
-  (host-environment
-   (list '())
-   "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
-@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
-possible to securely set values in the container environment.  This field's
-value can be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to @code{make-forkexec-constructor}."
-   (sanitizer oci-sanitize-host-environment))
-  (environment
-   (list '())
-   "Set environment variables inside the container.  This can be a list of pairs
-or strings, even mixed:
-
-@lisp
-(list '(\"LANGUAGE\" . \"eo:ca:eu\")
-      \"JAVA_HOME=/opt/java\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-environment))
-  (image
-   (string-or-oci-image)
-   "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
-@code{myregistry.local:5000/testing/test-image:tag}.")
-  (provision
-   (maybe-string)
-   "Set the name of the provisioned Shepherd service.")
-  (requirement
-   (list-of-symbols '())
-   "Set additional Shepherd services dependencies to the provisioned Shepherd
-service.")
-  (log-file
-   (maybe-string)
-   "When @code{log-file} is set, it names the file to which the service’s
-standard output and standard error are redirected.  @code{log-file} is created
-if it does not exist, otherwise it is appended to.")
-  (auto-start?
-   (boolean #t)
-   "Whether this service should be started automatically by the Shepherd.  If it
-is @code{#f} the service has to be started manually with @command{herd start}.")
-  (respawn?
-   (boolean #f)
-   "Whether to restart the service when it stops, for instance when the
-underlying process dies.")
-  (shepherd-actions
-   (list '())
-   "This is a list of @code{shepherd-action} records defining actions supported
-by the service."
-   (sanitizer oci-sanitize-shepherd-actions))
-  (network
-   (maybe-string)
-   "Set a Docker network for the spawned container.")
-  (ports
-   (list '())
-   "Set the port or port ranges to expose from the spawned container.  This can
-be a list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"8080\" . \"80\")
-      \"10443:443\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-ports))
-  (volumes
-   (list '())
-   "Set volume mappings for the spawned container.  This can be a
-list of pairs or strings, even mixed:
-
-@lisp
-(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
-      \"/gnu/store:/gnu/store\")
-@end lisp
-
-Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
-   (sanitizer oci-sanitize-volumes))
-  (container-user
-   (maybe-string)
-   "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
-  (workdir
-   (maybe-string)
-   "Set the current working for the spawned Shepherd service.
-You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
-  (extra-arguments
-   (list '())
-   "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
-   (sanitizer oci-sanitize-extra-arguments)))
-
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
-
-(define* (get-keyword-value args keyword #:key (default #f))
-  (let ((kv (memq keyword args)))
-    (if (and kv (>= (length kv) 2))
-        (cadr kv)
-        default)))
-
-(define (lower-operating-system os target system)
-  (mlet* %store-monad
-      ((tarball
-        (lower-object
-         (system-image (os->image os #:type docker-image-type))
-         system
-         #:target target)))
-    (return tarball)))
-
-(define (lower-manifest name image target system)
-  (define value (oci-image-value image))
-  (define options (oci-image-pack-options image))
-  (define image-reference
-    (oci-image-reference image))
-  (define image-tag
-    (let* ((extra-options
-            (get-keyword-value options #:extra-options))
-           (image-tag-option
-            (and extra-options
-                 (get-keyword-value extra-options #:image-tag))))
-      (if image-tag-option
-          '()
-          `(#:extra-options (#:image-tag ,image-reference)))))
-
-  (mlet* %store-monad
-      ((_ (set-grafting
-           (oci-image-grafts? image)))
-       (guile (set-guile-for-build (default-guile)))
-       (profile
-        (profile-derivation value
-                            #:target target
-                            #:system system
-                            #:hooks '()
-                            #:locales? #f))
-       (tarball (apply pack:docker-image
-                       `(,name ,profile
-                         ,@options
-                         ,@image-tag
-                         #:localstatedir? #t))))
-    (return tarball)))
-
-(define (lower-oci-image name image)
-  (define value (oci-image-value image))
-  (define image-target (oci-image-target image))
-  (define image-system (oci-image-system image))
-  (define target
-    (if (maybe-value-set? image-target)
-        image-target
-        (%current-target-system)))
-  (define system
-    (if (maybe-value-set? image-system)
-        image-system
-        (%current-system)))
-  (with-store store
-   (run-with-store store
-     (match value
-       ((? manifest? value)
-        (lower-manifest name image target system))
-       ((? operating-system? value)
-        (lower-operating-system value target system))
-       ((or (? gexp? value)
-            (? file-like? value))
-        value)
-       (_
-        (raise
-         (formatted-message
-          (G_ "oci-image value must contain only manifest,
-operating-system, gexp or file-like records but ~a was found")
-          value))))
-     #:target target
-     #:system system)))
-
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
-    (with-imported-modules '((guix build utils))
-      (program-file (format #f "~a-image-loader" name)
-       #~(begin
-           (use-modules (guix build utils)
-                        (ice-9 popen)
-                        (ice-9 rdelim))
-
-           (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
-           (define line
-             (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
-
-           (unless (or (eof-object? line)
-                       (string-null? line))
-             (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
-
-               (invoke #$docker "tag" repository&tag #$tag)
-               (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
-
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
-         (auto-start?
-          (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
-         (host-environment
-          (oci-container-configuration-host-environment config))
-         (command (oci-container-configuration-command config))
-         (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
-         (requirement (oci-container-configuration-requirement config))
-         (respawn?
-          (oci-container-configuration-respawn? config))
-         (image (oci-container-configuration-image config))
-         (image-reference (oci-image-reference image))
-         (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
-         (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
-
-    (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
-                      (respawn? respawn?)
-                      (auto-start? auto-start?)
-                      (documentation
-                       (string-append
-                        "Docker backed Shepherd service for "
-                        (if (oci-image? image) name image) "."))
-                      (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
-                           (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
-                            #:user #$user
-                            #:group #$group
-                            #$@(if (maybe-value-set? log-file)
-                                   (list #:log-file log-file)
-                                   '())
-                            #:environment-variables
-                            (list #$@host-environment))))
-                      (stop
-                       #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
-                      (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
-                            (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
-  (list (user-account
-         (name "oci-container")
-         (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
-         (shell (file-append shadow "/sbin/nologin")))))
-
 (define (configs->shepherd-services configs)
   (map oci-container-shepherd-service configs))
 
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 90c8d0f8508..5dcf05a17e3 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -1,7 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2019 Danny Milosavljevic <dannym@HIDDEN>
 ;;; Copyright © 2019-2023 Ludovic Courtès <ludo@HIDDEN>
-;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@HIDDEN>
+;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@HIDDEN>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -414,71 +414,54 @@ (define (run-oci-container-test)
           (test-runner-current (system-test-runner #$output))
           (test-begin "oci-container")
 
-          (test-assert "containerd service running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'containerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (test-assert "containerd PID file present"
-            (wait-for-file "/run/containerd/containerd.pid" marionette))
-
-          (test-assert "dockerd running"
-            (marionette-eval
-             '(begin
-                (use-modules (gnu services herd))
-                (match (start-service 'dockerd)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
-             marionette))
-
-          (sleep 10) ; let service start
+          (wait-for-file "/run/containerd/containerd.pid" marionette)
 
           (test-assert "docker-guile running"
             (marionette-eval
              '(begin
                 (use-modules (gnu services herd))
-                (match (start-service 'docker-guile)
-                  (#f #f)
-                  (('service response-parts ...)
-                   (match (assq-ref response-parts 'running)
-                     ((pid) pid)))))
+                (wait-for-service 'docker-guile #:timeout 120)
+                #t)
              marionette))
 
-          (test-equal "passing host environment variables and volumes"
-            '("value" "hello")
-            (marionette-eval
-             `(begin
-                (use-modules (ice-9 popen)
-                             (ice-9 rdelim))
-
-                (define slurp
-                  (lambda args
-                    (let* ((port (apply open-pipe* OPEN_READ args))
-                           (output (let ((line (read-line port)))
-                                     (if (eof-object? line)
-                                         ""
-                                         line)))
-                           (status (close-pipe port)))
-                      output)))
-                (let* ((response1 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
-                       (response2 (slurp
-                                   ,(string-append #$docker-cli "/bin/docker")
-                                   "exec" "docker-guile"
-                                   "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+          (test-assert "passing host environment variables and volumes"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+                    (let* ((response1 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                           (response2 (slurp
+                                       ,(string-append #$docker-cli "/bin/docker")
+                                       "exec" "docker-guile"
+                                       "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
 (display (call-with-input-file \"/shared.txt\" read-line)))")))
-                  (list response1 response2)))
-             marionette))
+                      (list response1 response2)))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 60)
+                    (error "Service didn't come up after more than 60 seconds")
+                    (if (equal? '("value" "hello")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
 
           (test-end))))
 
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 Feb 2025 22:03:17 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 05 17:03:17 2025
Received: from localhost ([127.0.0.1]:53029 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfnUL-0003rT-OV
	for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:17 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:22827)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnUH-0003qq-DV
 for 76081 <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:13 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1738792990;
 bh=ped8LKNxfBzK4wMnAaS/wU3XSRkLpK01Vt4clHpTnNw=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=l0FIhf9NUYPjmBQPPSgLzbzuiJJjB0UuO+dxFIsduOcRdPPwtuUVAiRarF98YSM+l
 1bAs6CoLOhKyVkRcrJitDhSB3XO/s9CfkLYKbbk2xRlvF2qBvBJnhQ/0kvsSjGR91W
 P+74wmbN5V78PsxyEurroOGsekSupkqwwGhylHDg=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YpDkk4PD9z115B;
 Wed,  5 Feb 2025 22:03:10 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YpDkk38WWz114x; Wed,  5 Feb 2025 22:03:10 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH 3/4] services: Add oci-service-type.
Date: Wed,  5 Feb 2025 23:02:30 +0100
Message-ID: <c3c8730fa100fd8b577c4aa9ed642254421920da.1738792951.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
X-Debbugs-Cc: Ludovic Courtès <ludo@HIDDEN>, Maxim Cournoyer <maxim.cournoyer@HIDDEN>
Content-Transfer-Encoding: 8bit
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This patch implements a generalization of the
oci-container-service-type, which consequently is made deprecated.  The
oci-service-type, in addition to all the features from the
oci-container-service-type, can now provision OCI networks and volumes.
It only handles OCI objects creation, the user is supposed to handle
state once the objects are provsioned.

It currently supports two different OCI runtimes: Docker and rootless
Podman.  Both runtimes are tested to make sure provisioned containers
can connect to each other through provisioned networks and can
read/write data with provisioned volumes.

At last the Scheme API is thought to facilitate the implementation of a
Guix Home service in the future.

* gnu/services/containers.scm (%oci-supported-runtimes): New variable;
(oci-runtime-cli): new variable;
(oci-runtime-name): new variable;
(oci-network-configuration): new variable;
(oci-volume-configuration): new variable;
(oci-configuration): new variable;
(oci-extension): new variable;
(oci-networks-shepherd-name): new variable;
(oci-service-type): new variable;
(oci-state->shepherd-services): new variable.
* doc/guix.texi: Document it.
* gnu/tests/containers.scm: Test it.
* gnu/services/docker.scm: Deprecate the oci-container-service-type.

Change-Id: I656b3db85832e42d53072fcbfb91d1226f39ef38
---
 doc/guix.texi               |  295 +++++++---
 gnu/services/containers.scm | 1038 +++++++++++++++++++++++++++++++----
 gnu/services/docker.scm     |   37 +-
 gnu/tests/containers.scm    |  956 +++++++++++++++++++++++++++++++-
 4 files changed, 2130 insertions(+), 196 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index bb5f29277fb..ff3e77a1d00 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -41778,59 +41778,155 @@ Miscellaneous Services
 @cindex OCI-backed, Shepherd services
 @subsubheading OCI backed services
 
-Should you wish to manage your Docker containers with the same consistent
-interface you use for your other Shepherd services,
-@var{oci-container-service-type} is the tool to use: given an
-@acronym{Open Container Initiative, OCI} container image, it will run it in a
+Should you wish to manage your @acronym{Open Container Initiative, OCI} containers
+with the same consistent interface you use for your other Shepherd services,
+@var{oci-service-type} is the tool to use: given an
+OCI container image, it will run it in a
 Shepherd service.  One example where this is useful: it lets you run services
-that are available as Docker/OCI images but not yet packaged for Guix.
+that are available as OCI images but not yet packaged for Guix.
 
-@defvar oci-container-service-type
+@defvar oci-service-type
 
-This is a thin wrapper around Docker's CLI that executes OCI images backed
+This is a thin wrapper around Docker's or Podman's CLI that executes OCI images backed
 processes as Shepherd Services.
 
 @lisp
-(service oci-container-service-type
-         (list
-          (oci-container-configuration
-           (network "host")
-           (image
-            (oci-image
-             (repository "guile")
-             (tag "3")
-             (value (specifications->manifest '("guile")))
-             (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
-                             #:max-layers 2))))
-           (entrypoint "/bin/guile")
-           (command
-            '("-c" "(display \"hello!\n\")")))
-          (oci-container-configuration
-           (image "prom/prometheus")
-           (ports
-             '(("9000" . "9000")
-               ("9090" . "9090"))))
-          (oci-container-configuration
-           (image "grafana/grafana:10.0.1")
-           (network "host")
-           (volumes
-             '("/var/lib/grafana:/var/lib/grafana")))))
+(simple-service 'oci-provisioning
+                oci-service-type
+                (oci-extension
+                  (networks
+                    (list
+                      (oci-network-configuration (name "monitoring"))))
+                  (containers
+                   (list
+                    (oci-container-configuration
+                     (network "monitoring")
+                     (image
+                      (oci-image
+                        (repository "guile")
+                        (tag "3")
+                        (value (specifications->manifest '("guile")))
+                        (pack-options '(#:symlinks (("/bin/guile" -> "bin/guile"))
+                                        #:max-layers 2))))
+                     (entrypoint "/bin/guile")
+                     (command
+                      '("-c" "(display \"hello!\n\")")))
+                    (oci-container-configuration
+                      (image "prom/prometheus")
+                      (network "host")
+                      (ports
+                       '(("9000" . "9000")
+                         ("9090" . "9090"))))
+                    (oci-container-configuration
+                      (image "grafana/grafana:10.0.1")
+                      (network "host")
+                      (volumes
+                       '("/var/lib/grafana:/var/lib/grafana")))))))
 @end lisp
 
 In this example three different Shepherd services are going to be added to the
 system.  Each @code{oci-container-configuration} record translates to a
-@code{docker run} invocation and its fields directly map to options.  You can
-refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run,upstream}
-documentation for the semantics of each value.  If the images are not found,
-they will be
-@url{https://docs.docker.com/engine/reference/commandline/pull/,pulled}.  The
+@command{docker run} or @command{podman run} invocation and its fields directly
+map to options.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html,Podman}
+upstream documentation for semantics of each value.  If the images are not found,
+they will be pulled.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/pull/,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-pull.1.html,Podman}
+upstream documentation for semantics.  The
 services with @code{(network "host")} are going to be attached to the
 host network and are supposed to behave like native processes with regard to
 networking.
 
 @end defvar
 
+@c %start of fragment
+
+@deftp {Data Type} oci-configuration
+Available @code{oci-configuration} fields are:
+
+@table @asis
+@item @code{runtime} (default: @code{'docker}) (type: symbol)
+The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}.
+
+@item @code{runtime-cli} (type: maybe-package)
+The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.
+
+@item @code{user} (default: @code{"oci-container"}) (type: string)
+The user name under whose authority OCI commands will be run.
+
+@item @code{group} (default: @code{"docker"}) (type: string)
+The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.
+
+@item @code{subuids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{subgids-range} (type: maybe-subid-range)
+An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name "oci-container"))}.
+
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.
+
+@item @code{verbose?} (default: @code{#f}) (type: boolean)
+When true, additional output will be printed, allowing to better follow the
+flow of execution.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-extension
+Available @code{oci-extension} fields are:
+
+@table @asis
+@item @code{containers} (default: @code{'()}) (type: list-of-oci-containers)
+The list of @code{oci-container-configuration} records representing the
+containers to provision.
+
+@item @code{networks} (default: @code{'()}) (type: list-of-oci-networks)
+The list of @code{oci-network-configuration} records representing the
+containers to provision.
+
+@item @code{volumes} (default: @code{'()}) (type: list-of-oci-volumes)
+The list of @code{oci-volumes-configuration} records representing the
+containers to provision.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+
 @c %start of fragment
 
 @deftp {Data Type} oci-container-configuration
@@ -41850,16 +41946,16 @@ Miscellaneous Services
 Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.
 
 @item @code{host-environment} (default: @code{'()}) (type: list)
-Set environment variables in the host environment where @command{docker
-run} is invoked.  This is especially useful to pass secrets from the
-host to the container without having them on the @command{docker run}'s
-command line: by setting the @code{MYSQL_PASSWORD} on the host and by passing
+Set environment variables in the host environment where @command{docker run}
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
 
 @lisp
-(list '("LANGUAGE\" . "eo:ca:eu")
+(list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
@@ -41867,22 +41963,24 @@ Miscellaneous Services
 directly to @code{make-forkexec-constructor}.
 
 @item @code{environment} (default: @code{'()}) (type: list)
-Set environment variables. This can be a list of pairs or strings, even mixed:
+Set environment variables inside the container.  This can be a list of pairs
+or strings, even mixed:
 
 @lisp
 (list '("LANGUAGE" . "eo:ca:eu")
       "JAVA_HOME=/opt/java")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics.
 
 @item @code{image} (type: string-or-oci-image)
 The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker Engine, and
-follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.
 
 @item @code{provision} (default: @code{""}) (type: string)
@@ -41910,7 +42008,7 @@ Miscellaneous Services
 by the service.
 
 @item @code{network} (default: @code{""}) (type: string)
-Set a Docker network for the spawned container.
+Set an OCI network for the spawned container.
 
 @item @code{ports} (default: @code{'()}) (type: list)
 Set the port or port ranges to expose from the spawned container.  This can be a
@@ -41921,10 +42019,11 @@ Miscellaneous Services
       "10443:443")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics.
 
 @item @code{volumes} (default: @code{'()}) (type: list)
 Set volume mappings for the spawned container.  This can be a
@@ -41935,25 +42034,95 @@ Miscellaneous Services
       "/gnu/store:/gnu/store")
 @end lisp
 
-Pair members can be strings, gexps or file-like objects.
-Strings are passed directly to the Docker CLI.  You can refer to the
-@uref{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics.
+Pair members can be strings, gexps or file-like objects. Strings are passed
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics.
 
 @item @code{container-user} (default: @code{""}) (type: string)
 Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.
 
 @item @code{workdir} (default: @code{""}) (type: string)
 Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly passed
+to the @command{docker run} or @command{podman run} invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-network-configuration
+Available @code{oci-network-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI network to provision.
+
+@item @code{driver} (type: maybe-string)
+The driver to manage the network.
+
+@item @code{gateway} (type: maybe-string)
+IPv4 or IPv6 gateway for the subnet.
+
+@item @code{internal?} (default: @code{#f}) (type: boolean)
+Restrict external access to the network
+
+@item @code{ip-range} (type: maybe-string)
+Allocate container ip from a sub-range in CIDR format.
+
+@item @code{ipam-driver} (type: maybe-string)
+IP Address Management Driver.
+
+@item @code{ipv6?} (default: @code{#f}) (type: boolean)
+Enable IPv6 networking.
+
+@item @code{subnet} (type: maybe-string)
+Subnet in CIDR format that represents a network segment.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
+
+@item @code{extra-arguments} (default: @code{'()}) (type: list)
+A list of strings, gexps or file-like objects that will be directly
+passed to the runtime invokation.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} oci-volume-configuration
+Available @code{oci-volume-configuration} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+The name of the OCI volume to provision.
+
+@item @code{labels} (default: @code{'()}) (type: list)
+The list of labels that will be used to tag the current volume.
 
 @item @code{extra-arguments} (default: @code{'()}) (type: list)
 A list of strings, gexps or file-like objects that will be directly
-passed to the @command{docker run} invocation.
+passed to the runtime invokation.
 
 @end table
 
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 50bf6c05549..c45f79c4ed1 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -41,6 +41,7 @@ (define-module (gnu services containers)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
   #:use-module (srfi srfi-1)
+  #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:export (rootless-podman-configuration
             rootless-podman-configuration?
@@ -96,8 +97,68 @@ (define-module (gnu services containers)
             oci-container-configuration-workdir
             oci-container-configuration-extra-arguments
 
+            list-of-oci-containers?
+            list-of-oci-networks?
+            list-of-oci-volumes?
+
+            %oci-supported-runtimes
+            oci-sanitize-runtime
+            oci-runtime-system-requirement
+            oci-runtime-cli
+            oci-runtime-system-cli
+            oci-runtime-name
+            oci-runtime-group
+
+            oci-network-configuration
+            oci-network-configuration?
+            oci-network-configuration-fields
+            oci-network-configuration-name
+            oci-network-configuration-driver
+            oci-network-configuration-gateway
+            oci-network-configuration-internal?
+            oci-network-configuration-ip-range
+            oci-network-configuration-ipam-driver
+            oci-network-configuration-ipv6?
+            oci-network-configuration-subnet
+            oci-network-configuration-labels
+            oci-network-configuration-extra-arguments
+
+            oci-volume-configuration
+            oci-volume-configuration?
+            oci-volume-configuration-fields
+            oci-volume-configuration-name
+            oci-volume-configuration-labels
+            oci-volume-configuration-extra-arguments
+
+            oci-configuration
+            oci-configuration?
+            oci-configuration-fields
+            oci-configuration-runtime
+            oci-configuration-runtime-cli
+            oci-configuration-user
+            oci-configuration-group
+            oci-configuration-containers
+            oci-configuration-networks
+            oci-configuration-volumes
+            oci-configuration-verbose?
+
+            oci-extension
+            oci-extension?
+            oci-extension-fields
+            oci-extension-containers
+            oci-extension-networks
+            oci-extension-volumes
+
+            oci-networks-shepherd-name
+            oci-volumes-shepherd-name
+
             oci-container-shepherd-service
-            %oci-container-accounts))
+            oci-service-type
+            oci-service-accounts
+            oci-service-profile
+            oci-service-subids
+            oci-state->shepherd-services
+            oci-configuration->shepherd-services))
 
 (define (gexp-or-string? value)
   (or (gexp? value)
@@ -294,9 +355,42 @@ (define rootless-podman-service-type
 
 
 ;;;
-;;; OCI container.
+;;; OCI provisioning service.
 ;;;
 
+(define %oci-supported-runtimes
+  '(docker podman))
+
+(define (oci-runtime-system-requirement runtime)
+  "Return a list of Shepherd service names required by a given OCI runtime,
+before it is able to run containers."
+  (if (eq? 'podman runtime)
+      '(cgroups2-fs-owner cgroups2-limits
+        rootless-podman-shared-root-fs)
+      '(dockerd)))
+
+(define (oci-runtime-name runtime)
+  "Return a human readable name for a given OCI runtime."
+  (if (eq? 'podman runtime)
+      "Podman" "Docker"))
+
+(define (oci-runtime-group runtime maybe-group)
+  "Implement the logic behind selection of the group that is to be used by
+Shepherd to execute OCI commands."
+  (if (maybe-value-set? maybe-group)
+      maybe-group
+      (if (eq? 'podman runtime)
+          "cgroup"
+          "docker")))
+
+(define (oci-sanitize-runtime value)
+  (unless (member value %oci-supported-runtimes)
+    (raise
+     (formatted-message
+      (G_ "OCI runtime must be a symbol and one of ~a,
+but ~a was found") %oci-supported-runtimes value)))
+  value)
+
 (define (oci-sanitize-pair pair delimiter)
   (define (valid? member)
     (or (string? member)
@@ -345,6 +439,11 @@ (define (oci-sanitize-volumes value)
   ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
   (oci-sanitize-mixed-list "volumes" value ":"))
 
+(define (oci-sanitize-labels value)
+  ;; Expected spec format:
+  ;; '(("foo" . "bar") "foo=bar")
+  (oci-sanitize-mixed-list "labels" value "="))
+
 (define (oci-sanitize-shepherd-actions value)
   (map
    (lambda (el)
@@ -372,6 +471,7 @@ (define (oci-sanitize-extra-arguments value)
    value))
 
 (define (oci-image-reference image)
+  "Return a string OCI image reference representing IMAGE."
   (if (string? image)
       image
       (string-append (oci-image-repository image)
@@ -390,7 +490,19 @@ (define (string-or-oci-image? image)
 (define list-of-symbols?
   (list-of symbol?))
 
+(define (list-of-oci-records? name predicate value)
+  (map
+   (lambda (el)
+     (if (predicate el)
+         el
+         (raise
+          (formatted-message
+           (G_ "~a contains an illegal value: ~a") name el))))
+   value))
+
 (define-maybe/no-serialization string)
+(define-maybe/no-serialization package)
+(define-maybe/no-serialization subid-range)
 
 (define-configuration/no-serialization oci-image
   (repository
@@ -436,10 +548,12 @@ (define-configuration/no-serialization oci-image
 (define-configuration/no-serialization oci-container-configuration
   (user
    (string "oci-container")
-   "The user under whose authority docker commands will be run.")
+   "The user name under whose authority OCI commands will be run.")
   (group
    (string "docker")
-   "The group under whose authority docker commands will be run.")
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
   (command
    (list-of-strings '())
    "Overwrite the default command (@code{CMD}) of the image.")
@@ -449,9 +563,9 @@ (define-configuration/no-serialization oci-container-configuration
   (host-environment
    (list '())
    "Set environment variables in the host environment where @command{docker run}
-is invoked.  This is especially useful to pass secrets from the host to the
-container without having them on the @command{docker run}'s command line: by
-setting the @code{MYSQL_PASSWORD} on the host and by passing
+or @command{podman run} are invoked.  This is especially useful to pass secrets
+from the host to the container without having them on the OCI runtime command line,
+for example: by setting the @code{MYSQL_PASSWORD} on the host and by passing
 @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
 possible to securely set values in the container environment.  This field's
 value can be a list of pairs or strings, even mixed:
@@ -475,15 +589,16 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#env,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#env-e-env,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-environment))
   (image
    (string-or-oci-image)
    "The image used to build the container.  It can be a string or an
-@code{oci-image} record.  Strings are resolved by the Docker
-Engine, and follow the usual format
+@code{oci-image} record.  Strings are resolved by the OCI runtime,
+and follow the usual format
 @code{myregistry.local:5000/testing/test-image:tag}.")
   (provision
    (maybe-string)
@@ -512,7 +627,7 @@ (define-configuration/no-serialization oci-container-configuration
    (sanitizer oci-sanitize-shepherd-actions))
   (network
    (maybe-string)
-   "Set a Docker network for the spawned container.")
+   "Set an OCI network for the spawned container.")
   (ports
    (list '())
    "Set the port or port ranges to expose from the spawned container.  This can
@@ -524,9 +639,10 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#publish,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#publish-p-ip-hostport-containerport-protocol,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-ports))
   (volumes
    (list '())
@@ -539,63 +655,307 @@ (define-configuration/no-serialization oci-container-configuration
 @end lisp
 
 Pair members can be strings, gexps or file-like objects. Strings are passed
-directly to the Docker CLI.  You can refer to the
-@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
-documentation for semantics."
+directly to the OCI runtime CLI.  You can refer to the
+@url{https://docs.docker.com/engine/reference/commandline/run/#volume,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options,Podman}
+upstream documentation for semantics."
    (sanitizer oci-sanitize-volumes))
   (container-user
    (maybe-string)
    "Set the current user inside the spawned container.  You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#user,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#user,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#user-u-user-group,Podman}
+upstream documentation for semantics.")
   (workdir
    (maybe-string)
-   "Set the current working for the spawned Shepherd service.
+   "Set the current working directory for the spawned Shepherd service.
 You can refer to the
-@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
-documentation for semantics.")
+@url{https://docs.docker.com/engine/reference/run/#workdir,Docker}
+or @url{https://docs.podman.io/en/stable/markdown/podman-run.1.html#workdir-w-dir,Podman}
+upstream documentation for semantics.")
   (extra-arguments
    (list '())
    "A list of strings, gexps or file-like objects that will be directly passed
-to the @command{docker run} invokation."
+to the @command{docker run} or @command{podman run} invokation."
    (sanitizer oci-sanitize-extra-arguments)))
 
-(define oci-container-configuration->options
-  (lambda (config)
-    (let ((entrypoint
-           (oci-container-configuration-entrypoint config))
-          (network
-           (oci-container-configuration-network config))
-          (user
-           (oci-container-configuration-container-user config))
-          (workdir
-           (oci-container-configuration-workdir config)))
-      (apply append
-             (filter (compose not unspecified?)
-                     `(,(if (maybe-value-set? entrypoint)
-                            `("--entrypoint" ,entrypoint)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "--env" spec))
-                         (oci-container-configuration-environment config))
-                       ,(if (maybe-value-set? network)
-                            `("--network" ,network)
-                            '())
-                       ,(if (maybe-value-set? user)
-                            `("--user" ,user)
-                            '())
-                       ,(if (maybe-value-set? workdir)
-                            `("--workdir" ,workdir)
-                            '())
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-p" spec))
-                         (oci-container-configuration-ports config))
-                       ,(append-map
-                         (lambda (spec)
-                           (list "-v" spec))
-                         (oci-container-configuration-volumes config))))))))
+(define (list-of-oci-containers? value)
+  (list-of-oci-records? "containers" oci-container-configuration? value))
+
+(define-configuration/no-serialization oci-volume-configuration
+  (name
+   (string)
+   "The name of the OCI volume to provision.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-volumes? value)
+  (list-of-oci-records? "volumes" oci-volume-configuration? value))
+
+(define-configuration/no-serialization oci-network-configuration
+  (name
+   (string)
+   "The name of the OCI network to provision.")
+  (driver
+   (maybe-string)
+   "The driver to manage the network.")
+  (gateway
+   (maybe-string)
+   "IPv4 or IPv6 gateway for the subnet.")
+  (internal?
+   (boolean #f)
+   "Restrict external access to the network")
+  (ip-range
+   (maybe-string)
+   "Allocate container ip from a sub-range in CIDR format.")
+  (ipam-driver
+   (maybe-string)
+   "IP Address Management Driver.")
+  (ipv6?
+   (boolean #f)
+   "Enable IPv6 networking.")
+  (subnet
+   (maybe-string)
+   "Subnet in CIDR format that represents a network segment.")
+  (labels
+   (list '())
+   "The list of labels that will be used to tag the current volume."
+   (sanitizer oci-sanitize-labels))
+  (extra-arguments
+   (list '())
+   "A list of strings, gexps or file-like objects that will be directly passed
+to the runtime invokation."
+   (sanitizer oci-sanitize-extra-arguments)))
+
+(define (list-of-oci-networks? value)
+  (list-of-oci-records? "networks" oci-network-configuration? value))
+
+(define-configuration/no-serialization oci-configuration
+  (runtime
+   (symbol 'docker)
+   "The OCI runtime to use to run commands.  It can be either @code{'docker} or
+@code{'podman}."
+   (sanitizer oci-sanitize-runtime))
+  (runtime-cli
+   (maybe-package)
+   "The OCI runtime command line to be installed in the system profile and used
+to provision OCI resources.  When unset it will default to @code{docker-cli}
+package for the @code{'docker} runtime or to @code{podman} package for the
+@code{'podman} runtime.")
+  (user
+   (string "oci-container")
+   "The user name under whose authority OCI runtime commands will be run.")
+  (group
+   (maybe-string)
+   "The group name under whose authority OCI commands will be run.  When
+using the @code{'podman} OCI runtime, this field will be ignored and the
+default group of the user configured in the @code{user} field will be used.")
+  (subuids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subuids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (subgids-range
+   (maybe-subid-range)
+   "An optional @code{subid-range} record allocating subgids for the user from
+the @code{user} field.  When unset, with the rootless Podman OCI runtime, it
+defaults to @code{(subid-range (name \"oci-container\"))}.")
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to provision.  Most users are supposed not to use this field and use
+the @code{oci-extension} record instead.")
+  (verbose?
+   (boolean #f)
+   "When true, additional output will be printed, allowing to better follow the
+flow of execution."))
+
+(define (oci-runtime-cli runtime runtime-cli path)
+  "Return a gexp that, when lowered, evaluates to the file system path of the OCI
+runtime command requested by the user."
+  (if (string? runtime-cli)
+      ;; It is a user defined absolute path
+      runtime-cli
+      #~(string-append
+         (if #$(maybe-value-set? runtime-cli)
+             #$runtime-cli
+             #$path)
+         (if #$(eq? 'podman runtime)
+             "/bin/podman"
+             "/bin/docker"))))
+
+(define* (oci-runtime-system-cli config #:key (path "/run/current-system/profile"))
+  (let ((runtime-cli
+         (oci-configuration-runtime-cli config))
+        (runtime
+         (oci-configuration-runtime config)))
+    (oci-runtime-cli runtime runtime-cli path)))
+
+(define-configuration/no-serialization oci-extension
+  (containers
+   (list-of-oci-containers '())
+   "The list of @code{oci-container-configuration} records representing the
+containers to add.")
+  (networks
+   (list-of-oci-networks '())
+   "The list of @code{oci-network-configuration} records representing the
+networks to add.")
+  (volumes
+   (list-of-oci-volumes '())
+   "The list of @code{oci-volume-configuration} records representing the
+volumes to add."))
+
+(define (oci-image->container-name image)
+  "Infer the name of an OCI backed Shepherd service from its OCI image."
+  (basename
+   (if (string? image)
+       (first (string-split image #\:))
+       (oci-image-repository image))))
+
+(define (oci-object-command-shepherd-action object-name invokation)
+  "Return a Shepherd action printing a given INVOKATION of an OCI command for the
+given OBJECT-NAME."
+  (shepherd-action
+   (name 'command-line)
+   (documentation
+    (format #f "Prints ~a's OCI runtime command line invokation."
+            object-name))
+   (procedure
+    #~(lambda _
+        (format #t "~a~%" #$invokation)))))
+
+(define (oci-container-shepherd-name runtime config)
+  "Return the name of an OCI backed Shepherd service based on CONFIG.
+The name configured in the configuration record is returned when
+CONFIG's name field has a value, otherwise a name is inferred from CONFIG's
+image field."
+  (define name (oci-container-configuration-provision config))
+  (define image (oci-container-configuration-image config))
+
+  (if (maybe-value-set? name)
+      name
+      (string-append (symbol->string runtime) "-"
+                     (oci-image->container-name image))))
+
+(define (oci-networks-shepherd-name runtime)
+  "Return the name of the OCI networks provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-networks"))
+
+(define (oci-volumes-shepherd-name runtime)
+  "Return the name of the OCI volumes provisioning Shepherd service based on
+RUNTIME."
+  (string-append (symbol->string runtime) "-volumes"))
+
+(define (oci-container-configuration->options config)
+  "Map CONFIG, an oci-container-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime run command."
+  (let ((entrypoint
+         (oci-container-configuration-entrypoint config))
+        (network
+         (oci-container-configuration-network config))
+        (user
+         (oci-container-configuration-container-user config))
+        (workdir
+         (oci-container-configuration-workdir config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? entrypoint)
+                          `("--entrypoint" ,entrypoint)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--env" spec))
+                       (oci-container-configuration-environment config))
+                     ,(if (maybe-value-set? network)
+                          `("--network" ,network)
+                          '())
+                     ,(if (maybe-value-set? user)
+                          `("--user" ,user)
+                          '())
+                     ,(if (maybe-value-set? workdir)
+                          `("--workdir" ,workdir)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-p" spec))
+                       (oci-container-configuration-ports config))
+                     ,(append-map
+                       (lambda (spec)
+                         (list "-v" spec))
+                       (oci-container-configuration-volumes config)))))))
+
+(define (oci-network-configuration->options config)
+  "Map CONFIG, an oci-network-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime network create command."
+  (let ((driver (oci-network-configuration-driver config))
+        (gateway
+         (oci-network-configuration-gateway config))
+        (internal?
+         (oci-network-configuration-internal? config))
+        (ip-range
+         (oci-network-configuration-ip-range config))
+        (ipam-driver
+         (oci-network-configuration-ipam-driver config))
+        (ipv6?
+         (oci-network-configuration-ipv6? config))
+        (subnet
+         (oci-network-configuration-subnet config)))
+    (apply append
+           (filter (compose not unspecified?)
+                   `(,(if (maybe-value-set? driver)
+                          `("--driver" ,driver)
+                          '())
+                     ,(if (maybe-value-set? gateway)
+                          `("--gateway" ,gateway)
+                          '())
+                     ,(if internal?
+                          `("--internal")
+                          '())
+                     ,(if (maybe-value-set? ip-range)
+                          `("--ip-range" ,ip-range)
+                          '())
+                     ,(if (maybe-value-set? ipam-driver)
+                          `("--ipam-driver" ,ipam-driver)
+                          '())
+                     ,(if ipv6?
+                          `("--ipv6")
+                          '())
+                     ,(if (maybe-value-set? subnet)
+                          `("--subnet" ,subnet)
+                          '())
+                     ,(append-map
+                       (lambda (spec)
+                         (list "--label" spec))
+                       (oci-network-configuration-labels config)))))))
+
+(define (oci-volume-configuration->options config)
+  "Map CONFIG, an oci-volume-configuration record, to a gexp that, upon
+lowering, will be evaluated to a list of strings containing command line options
+for the OCI runtime volume create command."
+  (append-map
+   (lambda (spec)
+     (list "--label" spec))
+   (oci-volume-configuration-labels config)))
 
 (define* (get-keyword-value args keyword #:key (default #f))
   (let ((kv (memq keyword args)))
@@ -604,6 +964,7 @@ (define* (get-keyword-value args keyword #:key (default #f))
         default)))
 
 (define (lower-operating-system os target system)
+  "Lower OS, an operating-system record, into a tarball containing an OCI image."
   (mlet* %store-monad
       ((tarball
         (lower-object
@@ -613,6 +974,7 @@ (define (lower-operating-system os target system)
     (return tarball)))
 
 (define (lower-manifest name image target system)
+  "Lower IMAGE, a manifest record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define options (oci-image-pack-options image))
   (define image-reference
@@ -645,6 +1007,7 @@ (define (lower-manifest name image target system)
     (return tarball)))
 
 (define (lower-oci-image name image)
+  "Lower IMAGE, a oci-image record, into a tarball containing an OCI image."
   (define value (oci-image-value image))
   (define image-target (oci-image-target image))
   (define image-system (oci-image-system image))
@@ -675,9 +1038,10 @@ (define (lower-oci-image name image)
      #:target target
      #:system system)))
 
-(define (%oci-image-loader name image tag)
-  (let ((docker (file-append docker-cli "/bin/docker"))
-        (tarball (lower-oci-image name image)))
+(define* (oci-image-loader runtime-cli name image tag #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to load IMAGE through RUNTIME-CLI and to tag it with TAG afterwards."
+  (let ((tarball (lower-oci-image name image)))
     (with-imported-modules '((guix build utils))
       (program-file (format #f "~a-image-loader" name)
        #~(begin
@@ -686,102 +1050,536 @@ (define (%oci-image-loader name image tag)
                         (ice-9 rdelim))
 
            (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
+           (define load-command
+             (string-append #$runtime-cli
+                            " load -i " #$tarball))
+           (when #$verbose?
+             (format #t "Running ~a~%" load-command))
            (define line
              (read-line
-              (open-input-pipe
-               (string-append #$docker " load -i " #$tarball))))
+              (open-input-pipe load-command)))
 
            (unless (or (eof-object? line)
                        (string-null? line))
              (format #t "~a~%" line)
-             (let ((repository&tag
-                    (string-drop line
-                                 (string-length
-                                   "Loaded image: "))))
+             (let* ((repository&tag
+                     (string-drop line
+                                  (string-length
+                                   "Loaded image: ")))
+                    (tag-command
+                     (list #$runtime-cli "tag" repository&tag #$tag)))
+
+               (when #$verbose?
+                 (format #t "Running~{ ~a~}~%" tag-command))
 
-               (invoke #$docker "tag" repository&tag #$tag)
+               (apply invoke tag-command)
                (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
 
-(define (oci-container-shepherd-service config)
-  (define (guess-name name image)
-    (if (maybe-value-set? name)
-        name
-        (string-append "docker-"
-                       (basename
-                        (if (string? image)
-                            (first (string-split image #\:))
-                            (oci-image-repository image))))))
-
-  (let* ((docker (file-append docker-cli "/bin/docker"))
-         (actions (oci-container-configuration-shepherd-actions config))
+(define* (oci-container-entrypoint runtime-cli name image image-reference
+                                   invokation #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to the entrypoint
+for the Shepherd service that will run IMAGE through RUNTIME-CLI."
+  (program-file
+   (string-append "oci-entrypoint-" name)
+   #~(begin
+       (use-modules (ice-9 format))
+       (when #$verbose?
+         (format #t "Running in verbose mode..."))
+       (define invokation (list #$@invokation))
+       #$@(if (oci-image? image)
+              #~((system*
+                  #$(oci-image-loader
+                     runtime-cli name image
+                     image-reference #:verbose? verbose?)))
+              #~())
+       (when #$verbose?
+         (format #t "Running~{ ~a~}~%" invokation))
+       (apply execlp invokation))))
+
+(define* (oci-container-shepherd-service runtime runtime-cli config
+                                         #:key
+                                         (oci-requirement '())
+                                         (user #f)
+                                         (group #f)
+                                         (verbose? #f))
+  "Return a Shepherd service object that will run the OCI container represented
+by CONFIG through RUNTIME-CLI."
+  (let* ((actions (oci-container-configuration-shepherd-actions config))
          (auto-start?
           (oci-container-configuration-auto-start? config))
-         (user (oci-container-configuration-user config))
-         (group (oci-container-configuration-group config))
+         (user (or user (oci-container-configuration-user config)))
+         (group (if (and group (maybe-value-set? group))
+                    group
+                    (oci-container-configuration-group config)))
          (host-environment
           (oci-container-configuration-host-environment config))
          (command (oci-container-configuration-command config))
          (log-file (oci-container-configuration-log-file config))
-         (provision (oci-container-configuration-provision config))
          (requirement (oci-container-configuration-requirement config))
          (respawn?
           (oci-container-configuration-respawn? config))
          (image (oci-container-configuration-image config))
          (image-reference (oci-image-reference image))
          (options (oci-container-configuration->options config))
-         (name (guess-name provision image))
+         (name
+          (oci-container-shepherd-name runtime config))
          (extra-arguments
-          (oci-container-configuration-extra-arguments config)))
+          (oci-container-configuration-extra-arguments config))
+         (invokation
+          ;; run [OPTIONS] IMAGE [COMMAND] [ARG...]
+          `(,runtime-cli ,runtime-cli "run"
+            "--rm" "--name" ,name
+            ,@options ,@extra-arguments
+            ,image-reference ,@command)))
 
     (shepherd-service (provision `(,(string->symbol name)))
-                      (requirement `(dockerd user-processes ,@requirement))
+                      (requirement `(,@(oci-runtime-system-requirement runtime)
+                                     user-processes
+                                     ,@oci-requirement
+                                     ,@requirement))
                       (respawn? respawn?)
                       (auto-start? auto-start?)
                       (documentation
                        (string-append
-                        "Docker backed Shepherd service for "
+                        (oci-runtime-name runtime) " backed Shepherd service for "
                         (if (oci-image? image) name image) "."))
                       (start
-                       #~(lambda ()
-                           #$@(if (oci-image? image)
-                                  #~((invoke #$(%oci-image-loader
-                                                name image image-reference)))
-                                  #~())
+                       #~(lambda _
                            (fork+exec-command
-                            ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-                            (list #$docker "run" "--rm" "--name" #$name
-                                  #$@options #$@extra-arguments
-                                  #$image-reference #$@command)
+                            (list
+                             #$(oci-container-entrypoint
+                                runtime-cli name image image-reference
+                                invokation #:verbose? verbose?))
                             #:user #$user
-                            #:group #$group
+                            #:group
+                            (if #$(eq? runtime 'podman)
+                                (group:name
+                                 (getgrgid
+                                  (passwd:gid (getpwnam #$user))))
+                                #$group)
                             #$@(if (maybe-value-set? log-file)
                                    (list #:log-file log-file)
                                    '())
                             #:environment-variables
-                            (list #$@host-environment))))
+                            (append
+                             (list #$@host-environment)
+                             (if #$(eq? runtime 'podman)
+                                 (list
+                                  (string-append
+                                   "HOME=" (passwd:dir (getpwnam #$user))))
+                                 '())))))
                       (stop
                        #~(lambda _
-                           (invoke #$docker "rm" "-f" #$name)))
+                           (invoke #$runtime-cli "rm" "-f" #$name)))
                       (actions
-                       (if (oci-image? image)
-                           '()
-                           (append
+                       (append
+                        (list
+                         (oci-object-command-shepherd-action
+                          name #~(string-join (cdr (list #$@invokation)) " ")))
+                        (if (oci-image? image)
+                            '()
                             (list
-                             (shepherd-action
-                              (name 'pull)
-                              (documentation
-                               (format #f "Pull ~a's image (~a)."
-                                       name image))
-                              (procedure
-                               #~(lambda _
-                                   (invoke #$docker "pull" #$image)))))
-                            actions))))))
-
-(define %oci-container-accounts
+                             (let ((service-name name))
+                               (shepherd-action
+                                (name 'pull)
+                                (documentation
+                                 (format #f "Pull ~a's image (~a)."
+                                         service-name image))
+                                (procedure
+                                 #~(lambda _
+                                     (invoke #$runtime-cli "pull" #$image)))))))
+                        actions)))))
+
+(define (oci-object-create-invokation object runtime-cli name options
+                                      extra-arguments)
+  "Return a gexp that, upon lowering, will evaluate to the OCI runtime
+invokation for creating networks and volumes."
+  ;; network|volume create [options] [NAME]
+  #~(list #$runtime-cli #$object "create"
+          #$@options #$@extra-arguments #$name))
+
+(define (format-oci-invokations invokations)
+  "Return a gexp that, upon lowering, will evaluate to a formatted message
+containing the INVOKATIONS that the OCI runtime will execute to provision
+networks or volumes."
+  #~(string-join (map (lambda (i) (string-join i " "))
+                      (list #$@invokations))
+                 "\n"))
+
+(define* (oci-object-create-script object runtime runtime-cli invokations
+                                   #:key (verbose? #f))
+  "Return a file-like object that, once lowered, will evaluate to a program able
+to create OCI networks and volumes through RUNTIME-CLI."
+  (define runtime-string (symbol->string runtime))
+  (program-file
+   (string-append runtime-string "-" object "s-create.scm")
+   #~(begin
+       (use-modules (ice-9 format)
+                    (ice-9 match)
+                    (ice-9 popen)
+                    (ice-9 rdelim)
+                    (srfi srfi-1))
+
+       (define (read-lines file-or-port)
+         (define (loop-lines port)
+           (let loop ((lines '()))
+             (match (read-line port)
+               ((? eof-object?)
+                (reverse lines))
+               (line
+                (loop (cons line lines))))))
+
+         (if (port? file-or-port)
+             (loop-lines file-or-port)
+             (call-with-input-file file-or-port
+               loop-lines)))
+
+       (define (object-exists? name)
+         (if (string=? #$runtime-string "podman")
+             (let ((command
+                    (list #$runtime-cli
+                          #$object "exists" name)))
+               (when #$verbose?
+                 (format #t "Running~{ ~a~}~%" command))
+               (define exit-code (status:exit-val (apply system* command)))
+               (when #$verbose?
+                 (format #t "Exit code: ~a~%" exit-code))
+               (equal? EXIT_SUCCESS exit-code))
+             (let ((command
+                    (string-append #$runtime-cli
+                                   " " #$object " ls --format "
+                                   "\"{{.Name}}\"")))
+               (when #$verbose?
+                 (format #t "Running ~a~%" command))
+               (member name (read-lines (open-input-pipe command))))))
+
+       (for-each
+        (lambda (invokation)
+          (define name (last invokation))
+          (if (object-exists? name)
+              (format #t "~a ~a ~a already exists, skipping creation.~%"
+                      #$(oci-runtime-name runtime) name #$object)
+              (begin
+                (when #$verbose?
+                  (format #t "Running~{ ~a~}~%" invokation))
+                (let ((exit-code (status:exit-val (apply system* invokation))))
+                  (when #$verbose?
+                    (format #t "Exit code: ~a~%" exit-code))))))
+        (list #$@invokations)))))
+
+(define* (oci-object-shepherd-service object runtime runtime-cli name requirement invokations
+                                      #:key
+                                      (user #f)
+                                      (group #f)
+                                      (verbose? #f))
+  "Return a Shepherd service object that will create the OBJECTs represented
+by INVOKATIONS through RUNTIME-CLI."
+  (shepherd-service (provision `(,(string->symbol name)))
+                    (requirement `(user-processes ,@requirement))
+                    (one-shot? #t)
+                    (documentation
+                     (string-append
+                      (oci-runtime-name runtime) " " object
+                      " provisioning service"))
+                    (start
+                     #~(lambda _
+                         (fork+exec-command
+                          (list
+                           #$(oci-object-create-script
+                              object runtime runtime-cli
+                              invokations
+                              #:verbose? verbose?))
+                          #:user #$user
+                          #:group
+                          (if #$(eq? runtime 'podman)
+                              (group:name
+                               (getgrgid
+                                (passwd:gid (getpwnam #$user))))
+                              #$group)
+                          #$@(if (eq? runtime 'podman)
+                                 (list
+                                  #:environment-variables
+                                  #~(list
+                                     (string-append
+                                      "HOME=" (passwd:dir (getpwnam #$user)))))
+                                 '()))))
+                    (actions
+                     (list
+                      (oci-object-command-shepherd-action
+                       name (format-oci-invokations invokations))))))
+
+(define* (oci-networks-shepherd-service runtime runtime-cli name networks
+                                        #:key
+                                        (user #f)
+                                        (group #f)
+                                        (runtime-requirement '())
+                                        (default-requirement '(networking))
+                                        (verbose? #f))
+  "Return a Shepherd service object that will create the networks represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (network)
+            (oci-object-create-invokation
+             "network" runtime-cli
+             (oci-network-configuration-name network)
+             (oci-network-configuration->options network)
+             (oci-network-configuration-extra-arguments network)))
+          networks)))
+
+    (oci-object-shepherd-service
+     "network" runtime runtime-cli name
+     (append default-requirement runtime-requirement) invokations
+     #:user user #:group group #:verbose? verbose?)))
+
+(define* (oci-volumes-shepherd-service runtime runtime-cli name volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (runtime-requirement '()))
+  "Return a Shepherd service object that will create the volumes represented
+in CONFIG."
+  (let ((invokations
+         (map
+          (lambda (volume)
+            (oci-object-create-invokation
+             "volume" runtime-cli
+             (oci-volume-configuration-name volume)
+             (oci-volume-configuration->options volume)
+             (oci-volume-configuration-extra-arguments volume)))
+          volumes)))
+
+    (oci-object-shepherd-service
+     "volume" runtime runtime-cli name runtime-requirement invokations
+     #:user user #:group group #:verbose? verbose?)))
+
+(define (oci-service-accounts config)
+  (define user (oci-configuration-user config))
+  (define maybe-group (oci-configuration-group config))
+  (define runtime (oci-configuration-runtime config))
   (list (user-account
-         (name "oci-container")
+         (name user)
          (comment "OCI services account")
-         (group "docker")
-         (system? #t)
-         (home-directory "/var/empty")
+         (group "users")
+         (supplementary-groups
+          (list (oci-runtime-group runtime maybe-group)))
+         (system? (eq? 'docker runtime))
+         (home-directory (if (eq? 'podman runtime)
+                             (string-append "/home/" user)
+                             "/var/empty"))
+         (create-home-directory? (eq? 'podman runtime))
          (shell (file-append shadow "/sbin/nologin")))))
+
+(define* (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                       #:key (user #f) (group #f) (verbose? #f)
+                                       (networks-name #f) (volumes-name #f)
+                                       (runtime-requirement '())
+                                       (networks-default-requirement '()))
+  (let* ((networks?
+          (> (length networks) 0))
+         (networks-requirement
+          (if networks?
+              (list
+               (string->symbol
+                (oci-networks-shepherd-name runtime)))
+              '()))
+         (volumes?
+          (> (length volumes) 0))
+         (volumes-requirement
+          (if volumes?
+              (list
+               (string->symbol
+                (oci-volumes-shepherd-name runtime)))
+              '())))
+    (append
+     (map
+      (lambda (c)
+        (oci-container-shepherd-service
+         runtime runtime-cli c
+         #:user user
+         #:group group
+         #:oci-requirement
+         (append networks-requirement volumes-requirement)
+         #:verbose? verbose?))
+      containers)
+     (if networks?
+         (list
+          (oci-networks-shepherd-service
+           runtime runtime-cli
+           (if (string? networks-name)
+               networks-name
+               (oci-networks-shepherd-name runtime))
+           networks
+           #:user user #:group group
+           #:default-requirement networks-default-requirement
+           #:runtime-requirement runtime-requirement
+           #:verbose? verbose?))
+         '())
+     (if volumes?
+         (list
+          (oci-volumes-shepherd-service runtime runtime-cli
+                                        (if (string? volumes-name)
+                                            volumes-name
+                                            (oci-volumes-shepherd-name runtime))
+                                        volumes
+                                        #:user user #:group group
+                                        #:runtime-requirement runtime-requirement
+                                        #:verbose? verbose?))
+         '()))))
+
+(define (oci-configuration->shepherd-services config)
+  (let* ((runtime (oci-configuration-runtime config))
+         (runtime-cli
+          (oci-runtime-system-cli config))
+         (containers (oci-configuration-containers config))
+         (networks (oci-configuration-networks config))
+         (volumes (oci-configuration-volumes config))
+         (user (oci-configuration-user config))
+         (group (oci-runtime-group
+                 runtime (oci-configuration-group config)))
+         (verbose? (oci-configuration-verbose? config)))
+    (oci-state->shepherd-services runtime runtime-cli containers networks volumes
+                                  #:user user #:group group #:verbose? verbose?
+                                  #:runtime-requirement
+                                  (oci-runtime-system-requirement runtime))))
+
+(define (oci-service-subids config)
+  "Return a subids-extension record representing subuids and subgids required by
+the rootless Podman backend."
+  (define (delete-duplicate-ranges ranges)
+    (delete-duplicates ranges
+                       (lambda args
+                         (apply string=? (map subid-range-name ranges)))))
+  (define runtime
+    (oci-configuration-runtime config))
+  (define user
+    (oci-configuration-user config))
+  (define subgids (oci-configuration-subgids-range config))
+  (define subuids (oci-configuration-subuids-range config))
+  (define container-users
+    (filter (lambda (range) (not (string=? (subid-range-name range) user)))
+            (map (lambda (container)
+                   (subid-range
+                    (name
+                     (oci-container-configuration-user container))))
+                 (oci-configuration-containers config))))
+  (define subgid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subgids)
+          subgids
+          (subid-range (name user)))
+      container-users)))
+  (define subuid-ranges
+    (delete-duplicate-ranges
+     (cons
+      (if (maybe-value-set? subuids)
+          subuids
+          (subid-range (name user)))
+      container-users)))
+
+  (if (eq? 'podman runtime)
+      (subids-extension
+       (subgids
+        subgid-ranges)
+       (subuids
+        subuid-ranges))
+      (subids-extension)))
+
+(define (oci-objects-merge-lst a b object get-name)
+  (define (contains? value lst)
+    (member value (map get-name lst)))
+  (let loop ((merged '())
+             (lst (append a b)))
+    (if (null? lst)
+        merged
+        (loop
+         (let ((element (car lst)))
+           (when (contains? element merged)
+             (raise
+              (formatted-message
+               (G_ "Duplicated ~a: ~a. ~as names should be unique, please
+remove the duplicate.") object (get-name element) object)))
+           (cons element merged))
+         (cdr lst)))))
+
+(define (oci-extension-merge a b)
+  (oci-extension
+   (containers (oci-objects-merge-lst
+                (oci-extension-containers a)
+                (oci-extension-containers b)
+                "container"
+                (lambda (config)
+                  (define maybe-name (oci-container-configuration-provision config))
+                  (if (maybe-value-set? maybe-name)
+                      maybe-name
+                      (oci-image->container-name
+                       (oci-container-configuration-image config))))))
+   (networks (oci-objects-merge-lst
+              (oci-extension-networks a)
+              (oci-extension-networks b)
+              "network"
+              oci-networks-shepherd-name))
+   (volumes (oci-objects-merge-lst
+             (oci-extension-volumes a)
+             (oci-extension-volumes b)
+             "volume"
+             oci-volumes-shepherd-name))))
+
+(define (oci-service-profile runtime runtime-cli)
+  (list bash-minimal
+        (cond
+         ((maybe-value-set? runtime-cli)
+          runtime-cli)
+         ((eq? 'podman runtime)
+          podman)
+         (else
+          docker-cli))))
+
+(define oci-service-type
+  (service-type (name 'oci)
+                (extensions
+                 (list
+                  (service-extension profile-service-type
+                                     (lambda (config)
+                                       (let ((runtime-cli
+                                              (oci-configuration-runtime-cli config))
+                                             (runtime
+                                              (oci-configuration-runtime config)))
+                                         (oci-service-profile runtime runtime-cli))))
+                  (service-extension subids-service-type
+                                     oci-service-subids)
+                  (service-extension account-service-type
+                                     oci-service-accounts)
+                  (service-extension shepherd-root-service-type
+                                     oci-configuration->shepherd-services)))
+                ;; Concatenate OCI object lists.
+                (compose (lambda (args)
+                           (fold oci-extension-merge
+                                 (oci-extension)
+                                 args)))
+                (extend
+                 (lambda (config extension)
+                   (oci-configuration
+                    (inherit config)
+                    (containers
+                     (oci-objects-merge-lst
+                      (oci-configuration-containers config)
+                      (oci-extension-containers extension)
+                      "container"
+                      (lambda (oci-config)
+                        (define runtime
+                          (oci-configuration-runtime config))
+                        (oci-container-shepherd-name runtime oci-config))))
+                    (networks (oci-objects-merge-lst
+                               (oci-configuration-networks config)
+                               (oci-extension-networks extension)
+                               "network"
+                               oci-networks-shepherd-name))
+                    (volumes (oci-objects-merge-lst
+                              (oci-configuration-volumes config)
+                              (oci-extension-volumes extension)
+                              "volume"
+                              oci-volumes-shepherd-name)))))
+                (default-value (oci-configuration))
+                (description
+                 "This service implements the provisioning of OCI object such
+as containers, networks and volumes.")))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 69ff9f1d9e4..98ee175873d 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -31,7 +31,10 @@ (define-module (gnu services docker)
   #:use-module (gnu system shadow)
   #:use-module (gnu packages docker)
   #:use-module (gnu packages linux)               ;singularity
+  #:use-module (guix deprecation)
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix records)
   #:use-module (srfi srfi-1)
   #:use-module (ice-9 format)
@@ -67,16 +70,18 @@ (define-module (gnu services docker)
                oci-container-configuration-volumes
                oci-container-configuration-container-user
                oci-container-configuration-workdir
-               oci-container-configuration-extra-arguments
-               oci-container-shepherd-service
-               %oci-container-accounts)
+               oci-container-configuration-extra-arguments)
 
   #:export (containerd-configuration
             containerd-service-type
             docker-configuration
             docker-service-type
             singularity-service-type
-            oci-container-service-type))
+            ;; for backwards compatibility, until the
+            ;; oci-container-service-type is fully deprecated
+            oci-container-shepherd-service
+            oci-container-service-type
+            %oci-container-accounts))
 
 (define-maybe file-like)
 
@@ -295,17 +300,25 @@ (define singularity-service-type
 ;;; OCI container.
 ;;;
 
-(define (configs->shepherd-services configs)
-  (map oci-container-shepherd-service configs))
+;; for backwards compatibility, until the
+;; oci-container-service-type is fully deprecated
+(define-deprecated (oci-container-shepherd-service config)
+  oci-service-type
+  ((@ (gnu services containers) oci-container-shepherd-service)
+   'docker config))
+(define %oci-container-accounts
+  (filter user-account? (oci-service-accounts (oci-configuration))))
 
 (define oci-container-service-type
   (service-type (name 'oci-container)
-                (extensions (list (service-extension profile-service-type
-                                                     (lambda _ (list docker-cli)))
-                                  (service-extension account-service-type
-                                                     (const %oci-container-accounts))
-                                  (service-extension shepherd-root-service-type
-                                                     configs->shepherd-services)))
+                (extensions
+                 (list (service-extension oci-service-type
+                                          (lambda (containers)
+                                            (warning
+                                             (G_
+                                              "'oci-container-service-type' is deprecated, use 'oci-service-type' instead~%"))
+                                            (oci-extension
+                                             (containers containers))))))
                 (default-value '())
                 (extend append)
                 (compose concatenate)
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 0ecc8ddb126..719647c298e 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -27,6 +27,9 @@ (define-module (gnu tests containers)
   #:use-module (gnu services)
   #:use-module (gnu services containers)
   #:use-module (gnu services desktop)
+  #:use-module ((gnu services docker)
+                #:select (containerd-service-type
+                          docker-service-type))
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu system)
@@ -39,7 +42,9 @@ (define-module (gnu tests containers)
   #:use-module (guix profiles)
   #:use-module ((guix scripts pack) #:prefix pack:)
   #:use-module (guix store)
-  #:export (%test-rootless-podman))
+  #:export (%test-rootless-podman
+            %test-oci-service-rootless-podman
+            %test-oci-service-docker))
 
 
 (define %rootless-podman-os
@@ -345,3 +350,952 @@ (define %test-rootless-podman
    (name "rootless-podman")
    (description "Test rootless Podman service.")
    (value (build-tarball&run-rootless-podman-test))))
+
+
+(define %guile-oci-image
+  (oci-image
+   (repository "guile")
+   (value
+    (specifications->manifest '("guile")))
+   (pack-options
+    '(#:symlinks (("/bin" -> "bin"))))))
+
+(define %oci-test-containers
+  (list
+   (oci-container-configuration
+    (provision "first")
+    (image %guile-oci-image)
+    (entrypoint "/bin/guile")
+    (network "my-network")
+    (command
+     '("-c" "(use-modules (web server))
+(define (handler request request-body)
+  (values '((content-type . (text/plain))) \"out of office\"))
+(run-server handler 'http `(#:addr ,(inet-pton AF_INET \"0.0.0.0\")))"))
+    (host-environment
+     '(("VARIABLE" . "value")))
+    (volumes
+     '(("my-volume" . "/my-volume")))
+    (extra-arguments
+     '("--env" "VARIABLE")))
+   (oci-container-configuration
+    (provision "second")
+    (image %guile-oci-image)
+    (entrypoint "/bin/guile")
+    (network "my-network")
+    (command
+     '("-c" "(let l ((c 300))(display c)(sleep 1)(when(positive? c)(l (- c 1))))"))
+    (volumes
+     '(("my-volume" . "/my-volume")
+       ("/shared.txt" . "/shared.txt:ro"))))))
+
+(define %oci-extension-test
+  (oci-extension
+   (networks
+    (list (oci-network-configuration (name "my-network"))))
+   (volumes
+    (list (oci-volume-configuration (name "my-volume"))))
+   (containers %oci-test-containers)))
+
+(define %oci-rootless-podman-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service iptables-service-type)
+   (service rootless-podman-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (runtime 'podman)
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   %oci-extension-test)))
+
+(define (run-rootless-podman-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-rootless-podman-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+          (define out-dir "/tmp")
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "rootless-podman-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'user-processes))
+           marionette)
+
+          (test-assert "rootless-podman services started successfully"
+           (begin
+             (define (run-test)
+               (marionette-eval
+                `(begin
+                   (use-modules (ice-9 popen)
+                                (ice-9 match)
+                                (ice-9 rdelim))
+
+                   (define (read-lines file-or-port)
+                     (define (loop-lines port)
+                       (let loop ((lines '()))
+                         (match (read-line port)
+                           ((? eof-object?)
+                            (reverse lines))
+                           (line
+                            (loop (cons line lines))))))
+
+                     (if (port? file-or-port)
+                         (loop-lines file-or-port)
+                         (call-with-input-file file-or-port
+                           loop-lines)))
+
+                   (define slurp
+                     (lambda args
+                       (let* ((port (apply open-pipe* OPEN_READ args))
+                              (output (read-lines port))
+                              (status (close-pipe port)))
+                         output)))
+                   (let* ((bash
+                           ,(string-append #$bash "/bin/bash"))
+                          (response1
+                           (slurp bash "-c"
+                                  (string-append "ls -la /sys/fs/cgroup | "
+                                                 "grep -E ' \\./?$' | awk '{ print $4 }'")))
+                          (response2 (slurp bash "-c"
+                                            (string-append "ls -l /sys/fs/cgroup/cgroup"
+                                                           ".{procs,subtree_control,threads} | "
+                                                           "awk '{ print $4 }' | sort -u"))))
+                     (list (string-join response1 "\n") (string-join response2 "\n"))))
+                marionette))
+             ;; Allow services to come up on slower machines
+             (let loop ((attempts 0))
+               (if (= attempts 60)
+                   (error "Services didn't come up after more than 60 seconds")
+                   (if (equal? '("cgroup" "cgroup")
+                               (run-test))
+                       #t
+                       (begin
+                         (sleep 1)
+                         (format #t "Services didn't come up yet, retrying with attempt ~a~%"
+                                 (+ 1 attempts))
+                         (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "volume" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "podman-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 6))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "network" "ls" "-n" "--format" "\"{{.Name}}\""
+                                            "|" "tr" "' '" "'\n'")))
+
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+
+                    (stable-sort
+                     (slurp "cat" (string-append ,out-dir "/response"))
+                     string<=?))
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-network" "podman")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+            (marionette-eval
+              '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'second #:timeout 120)
+                 #t)
+             marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (srfi srfi-1)
+                                 (ice-9 popen)
+                                 (ice-9 match)
+                                 (ice-9 rdelim))
+
+                    (define (wait-for-file file)
+                      ;; Wait until FILE shows up.
+                      (let loop ((i 60))
+                        (cond ((file-exists? file)
+                               #t)
+                              ((zero? i)
+                               (error "file didn't show up" file))
+                              (else
+                               (pk 'wait-for-file file)
+                               (sleep 1)
+                               (loop (- i 1))))))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (match (primitive-fork)
+                      (0
+                       (dynamic-wind
+                         (const #f)
+                         (lambda ()
+                           (setgid (passwd:gid (getpwnam "oci-container")))
+                           (setuid (passwd:uid (getpw "oci-container")))
+
+                           (let ((response (slurp
+                                            "/run/current-system/profile/bin/podman"
+                                            "exec" "first"
+                                            "/bin/guile" "-c" "'(display (getenv \"VARIABLE\"))'")))
+                             (call-with-output-file (string-append ,out-dir "/response")
+                               (lambda (port)
+                                 (display (string-join response "\n") port)))))
+                         (lambda ()
+                           (primitive-exit 127))))
+                      (pid
+                       (cdr (waitpid pid))))
+
+                    (wait-for-file (string-append ,out-dir "/response"))
+                    (slurp "cat" (string-append ,out-dir "/response")))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? (list "value")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            '("hello")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "write to volumes"
+            '("world")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (slurp
+                        "/run/current-system/profile/bin/podman"
+                        "exec" "first"
+                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))'")
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-equal "can read ports over network"
+            '("out of office")
+            (marionette-eval
+             `(begin
+                (use-modules (srfi srfi-1)
+                             (ice-9 popen)
+                             (ice-9 match)
+                             (ice-9 rdelim))
+
+                (define (wait-for-file file)
+                  ;; Wait until FILE shows up.
+                  (let loop ((i 60))
+                    (cond ((file-exists? file)
+                           #t)
+                          ((zero? i)
+                           (error "file didn't show up" file))
+                          (else
+                           (pk 'wait-for-file file)
+                           (sleep 1)
+                           (loop (- i 1))))))
+
+                (define (read-lines file-or-port)
+                  (define (loop-lines port)
+                    (let loop ((lines '()))
+                      (match (read-line port)
+                        ((? eof-object?)
+                         (reverse lines))
+                        (line
+                         (loop (cons line lines))))))
+
+                  (if (port? file-or-port)
+                      (loop-lines file-or-port)
+                      (call-with-input-file file-or-port
+                        loop-lines)))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ
+                                        (list "sh" "-l" "-c"
+                                              (string-join
+                                               args
+                                               " "))))
+                           (output (read-lines port))
+                           (status (close-pipe port)))
+                      output)))
+
+                (match (primitive-fork)
+                  (0
+                   (dynamic-wind
+                     (const #f)
+                     (lambda ()
+                       (setgid (passwd:gid (getpwnam "oci-container")))
+                       (setuid (passwd:uid (getpw "oci-container")))
+
+                       (let ((response (slurp
+                                        "/run/current-system/profile/bin/podman"
+                                        "exec" "second"
+                                        "/bin/guile" "-c" "'(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))'")))
+                         (call-with-output-file (string-append ,out-dir "/response")
+                           (lambda (port)
+                             (display (string-join response " ") port)))))
+                     (lambda ()
+                       (primitive-exit 127))))
+                  (pid
+                   (cdr (waitpid pid))))
+
+                (wait-for-file (string-append ,out-dir "/response"))
+                (slurp "cat" (string-append ,out-dir "/response")))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "rootless-podman-oci-service-test" test))
+
+(define %test-oci-service-rootless-podman
+  (system-test
+   (name "oci-service-rootless-podman")
+   (description "Test Rootless-Podman backed OCI provisioning service.")
+   (value (run-rootless-podman-oci-service-test))))
+
+(define %oci-docker-os
+  (simple-operating-system
+   (service dhcp-client-service-type)
+   (service dbus-root-service-type)
+   (service polkit-service-type)
+   (service elogind-service-type)
+   (service containerd-service-type)
+   (service docker-service-type)
+   (extra-special-file "/shared.txt"
+                       (plain-file "shared.txt" "hello"))
+   (service oci-service-type
+            (oci-configuration
+             (verbose? #t)))
+   (simple-service 'oci-provisioning
+                   oci-service-type
+                   %oci-extension-test)))
+
+(define (run-docker-oci-service-test)
+  (define os
+    (marionette-operating-system
+     (operating-system-with-gc-roots
+      %oci-docker-os
+      (list))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (volatile? #f)
+     (memory-size 1024)
+     (disk-image-size (* 3000 (expt 2 20)))
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-11) (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            ;; Relax timeout to accommodate older systems and
+            ;; allow for pulling the image.
+            (make-marionette (list #$vm) #:timeout 60))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "docker-oci-service")
+
+          (marionette-eval
+           '(begin
+              (use-modules (gnu services herd))
+              (wait-for-service 'dockerd))
+           marionette)
+
+          (test-assert "docker-volumes running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "volume" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("my-volume")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "docker-networks running"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define (read-lines file-or-port)
+                      (define (loop-lines port)
+                        (let loop ((lines '()))
+                          (match (read-line port)
+                            ((? eof-object?)
+                             (reverse lines))
+                            (line
+                             (loop (cons line lines))))))
+
+                      (if (port? file-or-port)
+                          (loop-lines file-or-port)
+                          (call-with-input-file file-or-port
+                            loop-lines)))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ
+                                            (list "sh" "-l" "-c"
+                                                  (string-join
+                                                   args
+                                                   " "))))
+                               (output (read-lines port))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (stable-sort
+                     (slurp
+                      "/run/current-system/profile/bin/docker"
+                      "network" "ls" "--format" "\"{{.Name}}\"")
+                     string<=?))
+
+                 marionette))
+              ;; Allow services to come up on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 80)
+                    (error "Service didn't come up after more than 80 seconds")
+                    (if (equal? '("bridge" "host" "my-network" "none")
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-assert "first container running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'first #:timeout 120)
+                #t)
+             marionette))
+
+          (test-assert "second container running"
+           (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'second #:timeout 120)
+               #t)
+            marionette))
+
+          (test-assert "passing host environment variables"
+            (begin
+              (define (run-test)
+                (marionette-eval
+                 `(begin
+                    (use-modules (ice-9 popen)
+                                 (ice-9 rdelim))
+
+                    (define slurp
+                      (lambda args
+                        (let* ((port (apply open-pipe* OPEN_READ args))
+                               (output (let ((line (read-line port)))
+                                         (if (eof-object? line)
+                                             ""
+                                             line)))
+                               (status (close-pipe port)))
+                          output)))
+
+                    (slurp
+                     "/run/current-system/profile/bin/docker"
+                     "exec" "first"
+                     "/bin/guile" "-c" "(display (getenv \"VARIABLE\"))"))
+                 marionette))
+              ;; Allow image to be loaded on slower machines
+              (let loop ((attempts 0))
+                (if (= attempts 180)
+                    (error "Service didn't come up after more than 180 seconds")
+                    (if (equal? "value"
+                                (run-test))
+                        #t
+                        (begin
+                          (sleep 1)
+                          (loop (+ 1 attempts))))))))
+
+          (test-equal "mounting host files"
+            "hello"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/shared.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "write to volumes"
+            "world"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "first"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(call-with-output-file \"/my-volume/out.txt\" (lambda (p) (display \"world\" p))))")
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (ice-9 popen) (ice-9 rdelim))
+(display (call-with-input-file \"/my-volume/out.txt\" read-line)))"))
+             marionette))
+
+          (test-equal "can read ports over network"
+            "out of office"
+            (marionette-eval
+             `(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 rdelim))
+
+                (define slurp
+                  (lambda args
+                    (let* ((port (apply open-pipe* OPEN_READ args))
+                           (output (let ((line (read-line port)))
+                                     (if (eof-object? line)
+                                         ""
+                                         line)))
+                           (status (close-pipe port)))
+                      output)))
+
+                (slurp
+                 "/run/current-system/profile/bin/docker"
+                 "exec" "second"
+                 "/bin/guile" "-c" "(begin (use-modules (web client))
+(define-values (response out)
+  (http-get \"http://first:8080\"))
+(display out))"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "docker-oci-service-test" test))
+
+(define %test-oci-service-docker
+  (system-test
+   (name "oci-service-docker")
+   (description "Test Docker backed OCI provisioning service.")
+   (value (run-docker-oci-service-test))))
-- 
2.48.1





Information forwarded to ludo@HIDDEN, maxim.cournoyer@HIDDEN, guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 Feb 2025 22:03:17 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 05 17:03:17 2025
Received: from localhost ([127.0.0.1]:53027 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfnUL-0003rR-1o
	for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:17 -0500
Received: from confino.investici.org ([2a11:7980:1::2:0]:27877)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnUG-0003qs-Ld
 for 76081 <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:13 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1738792990;
 bh=BhogBkcGveXGHkIi6ZMITQsZOgJcBwBmFiZeVz0FOd0=;
 h=From:To:Cc:Subject:Date:In-Reply-To:References:From;
 b=aXYYkGxXdWukx8AiwYCY2h6RdZzCzpj9hGbE5/XnYlxZUDrUctUXFr+12KjWiKqL1
 bjKHLVSQdk+8qxibiurPXru+2mhG8s6xnpPNkTnrjwoaRaV//I7Po+5ZYIn3Nuhn8c
 K5fQnskX26JtPq7zlZFxM1G/85BOHLd9eJb4unIs=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YpDkk6r0Vz115D;
 Wed,  5 Feb 2025 22:03:10 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YpDkk5syxz114x; Wed,  5 Feb 2025 22:03:10 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH 4/4] tests: Use oci-image in container tests.
Date: Wed,  5 Feb 2025 23:02:31 +0100
Message-ID: <d6b02a87a3827bec004de6a253c6f160a9f33578.1738792951.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
In-Reply-To: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
References: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.0 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This patch replaces boilerplate in container related tests with
oci-image plumbing from (gnu services containers).

* gnu/services/containers.scm: Export lower-oci-image.
* gnu/tests/containers.scm (%oci-tarball): New variable;
(run-rootless-podman-test): use %oci-tarball;
(build-tarball&run-rootless-podman-test): drop procedure.
* gnu/tests/docker.scm (%docker-tarball): New variable;
(build-tarball&run-docker-test): use %docker-tarball;
(%docker-system-tarball): New variable;
(build-tarball&run-docker-system-test): new procedure.

Change-Id: Iad6f0704aee188d89464c83722dea0bb7adb084a
---
 gnu/services/containers.scm |  2 +
 gnu/tests/containers.scm    | 80 ++++++++++++++----------------
 gnu/tests/docker.scm        | 98 ++++++++++++++++++++-----------------
 3 files changed, 93 insertions(+), 87 deletions(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index c45f79c4ed1..e15dbc6a21c 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -74,6 +74,8 @@ (define-module (gnu services containers)
             oci-image-system
             oci-image-grafts?
 
+            lower-oci-image
+
             oci-container-configuration
             oci-container-configuration?
             oci-container-configuration-fields
diff --git a/gnu/tests/containers.scm b/gnu/tests/containers.scm
index 719647c298e..024000bca14 100644
--- a/gnu/tests/containers.scm
+++ b/gnu/tests/containers.scm
@@ -69,13 +69,47 @@ (define %rootless-podman-os
                           (supplementary-groups '("wheel" "netdev" "cgroup"
                                                   "audio" "video")))))))
 
-(define (run-rootless-podman-test oci-tarball)
+(define %oci-tarball
+  (lower-oci-image
+   "guile-guest"
+   (oci-image
+    (repository "guile-guest")
+    (value
+     (packages->manifest
+      (list
+       guile-3.0 guile-json-3
+       (package
+         (name "guest-script")
+         (version "0")
+         (source #f)
+         (build-system trivial-build-system)
+         (arguments `(#:guile ,guile-3.0
+                      #:builder
+                      (let ((out (assoc-ref %outputs "out")))
+                        (mkdir out)
+                        (call-with-output-file (string-append out "/a.scm")
+                          (lambda (port)
+                            (display "(display \"hello world\n\")" port)))
+                        #t)))
+         (synopsis "Display hello world using Guile")
+         (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+         (home-page #f)
+         (license license:public-domain)))))
+    (pack-options
+     '(#:entry-point "bin/guile"
+       #:localstatedir? #t
+       #:extra-options (#:image-tag "guile-guest")
+       #:symlinks (("/bin/Guile" -> "bin/guile")
+                   ("aa.scm" -> "a.scm")))))))
+
+(define (run-rootless-podman-test)
 
   (define os
     (marionette-operating-system
      (operating-system-with-gc-roots
       %rootless-podman-os
-      (list oci-tarball))
+      (list %oci-tarball))
      #:imported-modules '((gnu services herd)
                           (guix combinators))))
 
@@ -254,7 +288,7 @@ (define (run-rootless-podman-test oci-tarball)
                        (let* ((loaded (slurp ,(string-append #$podman
                                                              "/bin/podman")
                                              "load" "-i"
-                                             ,#$oci-tarball))
+                                             ,#$%oci-tarball))
                               (repository&tag "localhost/guile-guest:latest")
                               (response1 (slurp
                                           ,(string-append #$podman "/bin/podman")
@@ -307,49 +341,11 @@ (define (run-rootless-podman-test oci-tarball)
 
   (gexp->derivation "rootless-podman-test" test))
 
-(define (build-tarball&run-rootless-podman-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:extra-options
-                 '(#:image-tag "guile-guest")
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-rootless-podman-test tarball)))
-
 (define %test-rootless-podman
   (system-test
    (name "rootless-podman")
    (description "Test rootless Podman service.")
-   (value (build-tarball&run-rootless-podman-test))))
+   (value (run-rootless-podman-test))))
 
 
 (define %guile-oci-image
diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm
index 5dcf05a17e3..d969b28a68f 100644
--- a/gnu/tests/docker.scm
+++ b/gnu/tests/docker.scm
@@ -26,6 +26,7 @@ (define-module (gnu tests docker)
   #:use-module (gnu system image)
   #:use-module (gnu system vm)
   #:use-module (gnu services)
+  #:use-module (gnu services containers)
   #:use-module (gnu services dbus)
   #:use-module (gnu services networking)
   #:use-module (gnu services docker)
@@ -57,6 +58,40 @@ (define %docker-os
    (service containerd-service-type)
    (service docker-service-type)))
 
+(define %docker-tarball
+  (lower-oci-image
+   "guile-guest"
+   (oci-image
+    (repository "guile-guest")
+    (value
+     (packages->manifest
+      (list
+       guile-3.0 guile-json-3
+       (package
+         (name "guest-script")
+         (version "0")
+         (source #f)
+         (build-system trivial-build-system)
+         (arguments `(#:guile ,guile-3.0
+                      #:builder
+                      (let ((out (assoc-ref %outputs "out")))
+                        (mkdir out)
+                        (call-with-output-file (string-append out "/a.scm")
+                          (lambda (port)
+                            (display "(display \"hello world\n\")" port)))
+                        #t)))
+         (synopsis "Display hello world using Guile")
+         (description "This package displays the text \"hello world\" on the
+standard output device and then enters a new line.")
+         (home-page #f)
+         (license license:public-domain)))))
+    (pack-options
+     '(#:entry-point "bin/guile"
+       #:localstatedir? #t
+       #:extra-options (#:image-tag "guile-guest")
+       #:symlinks (("/bin/Guile" -> "bin/guile")
+                   ("aa.scm" -> "a.scm")))))))
+
 (define (run-docker-test docker-tarball)
   "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
@@ -173,40 +208,7 @@ (define (run-docker-test docker-tarball)
   (gexp->derivation "docker-test" test))
 
 (define (build-tarball&run-docker-test)
-  (mlet* %store-monad
-      ((_ (set-grafting #f))
-       (guile (set-guile-for-build (default-guile)))
-       (guest-script-package ->
-        (package
-          (name "guest-script")
-          (version "0")
-          (source #f)
-          (build-system trivial-build-system)
-          (arguments `(#:guile ,guile-3.0
-                       #:builder
-                       (let ((out (assoc-ref %outputs "out")))
-                         (mkdir out)
-                         (call-with-output-file (string-append out "/a.scm")
-                           (lambda (port)
-                             (display "(display \"hello world\n\")" port)))
-                         #t)))
-          (synopsis "Display hello world using Guile")
-          (description "This package displays the text \"hello world\" on the
-standard output device and then enters a new line.")
-          (home-page #f)
-          (license license:public-domain)))
-       (profile (profile-derivation (packages->manifest
-                                     (list guile-3.0 guile-json-3
-                                           guest-script-package))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (pack:docker-image
-                 "docker-pack" profile
-                 #:symlinks '(("/bin/Guile" -> "bin/guile")
-                              ("aa.scm" -> "a.scm"))
-                 #:entry-point "bin/guile"
-                 #:localstatedir? #t)))
-    (run-docker-test tarball)))
+  (run-docker-test %docker-tarball))
 
 (define %test-docker
   (system-test
@@ -215,8 +217,20 @@ (define %test-docker
    (value (build-tarball&run-docker-test))))
 
 
+(define %docker-system-tarball
+  (lower-oci-image
+   "guix-system-guest"
+   (oci-image
+    (repository "guix-system-guest")
+    (value
+     (operating-system
+       (inherit (simple-operating-system))
+       ;; Use locales for a single libc to
+       ;; reduce space requirements.
+       (locale-libcs (list glibc)))))))
+
 (define (run-docker-system-test tarball)
-  "Load DOCKER-TARBALL as Docker image and run it in a Docker container,
+  "Load TARBALL as Docker image and run it in a Docker container,
 inside %DOCKER-OS."
   (define os
     (marionette-operating-system
@@ -333,21 +347,15 @@ (define (run-docker-system-test tarball)
 
   (gexp->derivation "docker-system-test" test))
 
+(define (build-tarball&run-docker-system-test)
+  (run-docker-system-test %docker-system-tarball))
+
 (define %test-docker-system
   (system-test
    (name "docker-system")
    (description "Run a system image as produced by @command{guix system
 docker-image} inside Docker.")
-   (value (with-monad %store-monad
-            (>>= (lower-object
-                  (system-image (os->image
-                                 (operating-system
-                                   (inherit (simple-operating-system))
-                                   ;; Use locales for a single libc to
-                                   ;; reduce space requirements.
-                                   (locale-libcs (list glibc)))
-                                 #:type docker-image-type)))
-                 run-docker-system-test)))))
+   (value (build-tarball&run-docker-system-test))))
 
 
 (define %oci-os
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at 76081 <at> debbugs.gnu.org:


Received: (at 76081) by debbugs.gnu.org; 5 Feb 2025 22:03:14 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 05 17:03:14 2025
Received: from localhost ([127.0.0.1]:53025 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfnUH-0003r2-IG
	for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:13 -0500
Received: from confino.investici.org ([93.190.126.19]:28453)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnUE-0003qn-8G
 for 76081 <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:03:11 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1738792988;
 bh=IdX9voWndJaRYrFOGe4a/X2LYvkwV0vTWt7K16N+Iog=;
 h=From:To:Cc:Subject:Date:From;
 b=btTSXS/GW9W68kDUMq4wVYIaDJmVURzpxd/SkEfRPhiVIwJDaJ/NrQTnEwUlNPW4f
 iECFdomAaz970EmiIP1OfA6Z6567TOswpDKBTFWXBYtLnErwh6Irxom4AsoFCwY7el
 m+Jksc5u5U2Xe76z1lXeKH0Ry5KhQc4hNMShCbEE=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YpDkh6CFFz113C;
 Wed,  5 Feb 2025 22:03:08 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YpDkh5BVMz114x; Wed,  5 Feb 2025 22:03:08 +0000 (UTC)
From: Giacomo Leidi <goodoldpaul@HIDDEN>
To: 76081 <at> debbugs.gnu.org
Subject: [PATCH 1/4] services: rootless-podman: Use login shell.
Date: Wed,  5 Feb 2025 23:02:28 +0100
Message-ID: <89778533f32ad1388f03414e884fff10f74ef379.1738792951.git.goodoldpaul@HIDDEN>
X-Mailer: git-send-email 2.48.1
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 76081
Cc: Giacomo Leidi <goodoldpaul@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

This commit allows for having PATH set when changing the owner of
/sys/fs/group.

* gnu/services/containers.scm (crgroups-fs-owner): Use login shell.

Change-Id: I9510c637a5332325e05ca5ebc9dfd4de32685c50
---
 gnu/services/containers.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index 19d35ccbcb6..dc66ac4f967 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -140,7 +140,7 @@ (define (cgroups-fs-owner-entrypoint config)
     (rootless-podman-configuration-group-name config))
   (program-file "cgroups2-fs-owner-entrypoint"
                 #~(system*
-                   (string-append #+bash-minimal "/bin/bash") "-c"
+                   (string-append #+bash-minimal "/bin/bash") "-l" "-c"
                    (string-append "echo Setting /sys/fs/cgroup "
                                   "group ownership to " #$group " && chown -v "
                                   "root:" #$group " /sys/fs/cgroup && "

base-commit: 5a897c5c95a81278b044c18d962d3bd83131ba06
-- 
2.48.1





Information forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 5 Feb 2025 22:00:18 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Feb 05 17:00:18 2025
Received: from localhost ([127.0.0.1]:53009 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tfnRR-0001Tn-Jx
	for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:00:18 -0500
Received: from lists.gnu.org ([2001:470:142::17]:38626)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnRP-000125-L0
 for submit <at> debbugs.gnu.org; Wed, 05 Feb 2025 17:00:16 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnRK-0001OT-08
 for guix-patches@HIDDEN; Wed, 05 Feb 2025 17:00:10 -0500
Received: from confino.investici.org ([93.190.126.19])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <goodoldpaul@HIDDEN>)
 id 1tfnRG-0006SU-SG
 for guix-patches@HIDDEN; Wed, 05 Feb 2025 17:00:09 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org;
 s=stigmate; t=1738792799;
 bh=UdxGZwt9f9DsHSe0cmS/2qQmmmaepGOJrFyU385OYAo=;
 h=Date:To:From:Subject:From;
 b=B/7u169gGjlOq8z/hPEUOrhxukKpO5RjOe12gFHr1b2H7HTJX1dRYdo6t+Q+928tZ
 YdPuwV3doGK8qjzq14XEe2IpAUAwoX+Qdb4wf8Es4kjWemefOo47UZUfkIaKDbDQLa
 xBapHEpDypWDzEmXkOOqMlwCRiFYqSdTphXurYrc=
Received: from mx1.investici.org (unknown [127.0.0.1])
 by confino.investici.org (Postfix) with ESMTP id 4YpDg3045Pz114y
 for <guix-patches@HIDDEN>; Wed,  5 Feb 2025 21:59:59 +0000 (UTC)
Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19])
 (Authenticated sender: goodoldpaul@HIDDEN) by localhost (Postfix) with
 ESMTPSA id 4YpDg26XYDz10yW
 for <guix-patches@HIDDEN>; Wed,  5 Feb 2025 21:59:58 +0000 (UTC)
Content-Type: multipart/alternative;
 boundary="------------4oR3n2evZzpt5qDyvI400v3d"
Message-ID: <2f43e635-508c-407a-8309-06e75d492d89@HIDDEN>
Date: Wed, 5 Feb 2025 22:59:57 +0100
MIME-Version: 1.0
User-Agent: Icedove Daily
To: guix-patches@HIDDEN
Content-Language: en-US
From: paul <goodoldpaul@HIDDEN>
Subject: OCI provisioning service
Received-SPF: pass client-ip=93.190.126.19;
 envelope-from=goodoldpaul@HIDDEN; helo=confino.investici.org
X-Spam_score_int: -27
X-Spam_score: -2.8
X-Spam_bar: --
X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001,
 RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,
 RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_PASS=-0.001,
 SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Spam-Score: 0.9 (/)
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -0.1 (/)

This is a multi-part message in MIME format.
--------------4oR3n2evZzpt5qDyvI400v3d
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

Hi Guix,

for a while now, we have been able to run Docker/OCI images as Shepherd 
services with the oci-container-service-type. This was useful especially 
to run software that is not packaged yet (or sometimes not packageable 
at all, due to JS and other bootstrapping issues). It allows to declare 
a list of oci-container-configuration records which would map to Docker 
backed Shepherd services:

(service oci-container-service-type
          (list
           (oci-container-configuration
            (image "prom/prometheus")
            (network "host")
            (ports
              '(("9000" . "9000")
                ("9090" . "9090"))))
           (oci-container-configuration
            (image "grafana/grafana:10.0.1")
            (network "host")
            (volumes
              '("/var/lib/grafana:/var/lib/grafana")))))

This allows us to have containerized but apparently native services 
running on the Guix System. The downside is that unless some isolation 
mechanism are disabled, or unless the running services are very simple 
and don't have to interact with the world outside them, we lose some of 
the nice virtualization features of containers. Above all is the need to 
have all containers connected to the host network to be able to interact 
with other containers. This is effectively the default behavior of most 
if not all Guix native services but with the downside that usually OCI 
images lack all the provenance information that is typical of Guix 
packages, hence are less trustable than a native Guix package so in some 
cases users do prefer to have them running in an isolated environment.

Another shortcoming of the oci-container-service-type is that it only 
supports Docker as an OCI runtime which must have a running daemon with 
full root privileges to be able to execute containers. Since some weeks 
now, with the help of subids/subgids and unprivileged namespaces, we are 
able to run completely rootless containers with the 
rootless-podman-service-type.

This patch implements a generalization of the 
oci-container-service-type, which consequently is made deprecated.  The 
oci-service-type, in addition to all the features from the 
oci-container-service-type, can now provision OCI networks and volumes:

(service iptables-service-type)
(service rootless-podman-service-type)
(service oci-service-type
          (oci-configuration
           (runtime 'podman)))
(simple-service 'oci-provisioning
                 oci-service-type
                 (oci-extension
                   (volumes
                     (list
                       (oci-network-configuration (name "grafana"))))
                   (networks
                     (list
                       (oci-network-configuration (name "monitoring"))))
                   (containers
                    (list
                     (oci-container-configuration
                       (image "docker.io/grafana/grafana:10.1.5")
                       (network "host")
                       (volumes
                        `(,( . "/opt/bitnami/grafana/conf/grafana.ini")
                          ("grafana" . "/var/lib/grafana"))))))))

Please mind that this is only an example and it probably won't work on 
your system without some more thought. The oci-service-type currently 
only handles OCI objects creation, the user is supposed to handle state 
once the objects are provsioned. It currently supports two different OCI 
runtimes: Docker and rootless Podman.  Both runtimes are tested to make 
sure provisioned containers can connect to each other through 
provisioned networks and can read/write data with provisioned volumes. 
Compared to the oci-container-service-type I added some utility Shepherd 
actions that are supposed to help with debugging. The above 
configuration would yield the following

$ sudo herd command-line podman-networks
/run/current-system/profile/bin/podman network create monitoring

$ sudo herd command-line podman-volumes
/run/current-system/profile/bin/podman volume create grafana

$ sudo herd doc podman-grafana list-actions

command-line:
   Prints podman-grafana's OCI runtime command line invokation.

pull:
   Pull podman-grafana's image (docker.io/grafana/grafana:10.1.5).

$ sudo herd command-line podman-grafana
/run/current-system/profile/bin/podman run --rm --name podman-grafana --network host -v grafana:/var/lib/grafana -v /gnu/store/yqfvhvf8j4008ykr52zh2dmc1d2mjxih-grafana.ini:/opt/bitnami/grafana/conf/grafana.ini docker.io/bitnami/grafana:10.1.5


At last the Scheme API is thought to facilitate the implementation of a 
Guix Home service in the future (you can find an untested version of 
that at [0]). I tested my changes with:

guix shell -D guix -CPW -- make check-system TESTS="oci-service-docker oci-container oci-service-rootless-podman docker docker-system rootless-podman"


Please let me know your thoughts about this!

Thank you for all your work,

giacomo


[0]: 
https://github.com/fishinthecalculator/gocix/blob/main/modules/oci/home/services/containers.scm

--------------4oR3n2evZzpt5qDyvI400v3d
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hi Guix,<br>
      <br>
      for a while now, we have been able to run Docker/OCI images as
      Shepherd services with the oci-container-service-type. This was
      useful especially to run software that is not packaged yet (or
      sometimes not packageable at all, due to JS and other
      bootstrapping issues). It allows to declare a list of
      oci-container-configuration records which would map to Docker
      backed Shepherd services:<br>
    </p>
    <pre>(service oci-container-service-type
         (list
          (oci-container-configuration
           (image "prom/prometheus")
           (network "host")
           (ports
             '(("9000" . "9000")
               ("9090" . "9090"))))
          (oci-container-configuration
           (image "grafana/grafana:10.0.1")
           (network "host")
           (volumes
             '("/var/lib/grafana:/var/lib/grafana")))))
</pre>
    <p>This allows us to have containerized but apparently native
      services running on the Guix System. The downside is that unless
      some isolation mechanism are disabled, or unless the running
      services are very simple and don't have to interact with the world
      outside them, we lose some of the nice virtualization features of
      containers. Above all is the need to have all containers connected
      to the host network to be able to interact with other containers.
      This is effectively the default behavior of most if not all Guix
      native services but with the downside that usually OCI images lack
      all the provenance information that is typical of Guix packages,
      hence are less trustable than a native Guix package so in some
      cases users do prefer to have them running in an isolated
      environment.</p>
    <p>Another shortcoming of the oci-container-service-type is that it
      only supports Docker as an OCI runtime which must have a running
      daemon with full root privileges to be able to execute containers.
      Since some weeks now, with the help of subids/subgids and
      unprivileged namespaces, we are able to run completely rootless
      containers with the rootless-podman-service-type.</p>
    <p>This patch implements a generalization of the
      oci-container-service-type, which consequently is made
      deprecated.  The oci-service-type, in addition to all the features
      from the oci-container-service-type, can now provision OCI
      networks and volumes:</p>
    <pre>(service iptables-service-type)
(service rootless-podman-service-type)
(service oci-service-type
         (oci-configuration
          (runtime 'podman)))
(simple-service 'oci-provisioning
                oci-service-type
                (oci-extension
                  (volumes
                    (list
                      (oci-network-configuration (name "grafana"))))
                  (networks
                    (list
                      (oci-network-configuration (name "monitoring"))))
                  (containers
                   (list
                    (oci-container-configuration
                      (image "docker.io/grafana/grafana:10.1.5")
                      (network "host")
                      (volumes
                       `(,( . "/opt/bitnami/grafana/conf/grafana.ini")
                         ("grafana" . "/var/lib/grafana"))))))))
</pre>
    <p>Please mind that this is only an example and it probably won't
      work on your system without some more thought. The
      oci-service-type currently only handles OCI objects creation, the
      user is supposed to handle state once the objects are provsioned.
      It currently supports two different OCI runtimes: Docker and
      rootless Podman.  Both runtimes are tested to make sure
      provisioned containers can connect to each other through
      provisioned networks and can read/write data with provisioned
      volumes. Compared to the oci-container-service-type I added some
      utility Shepherd actions that are supposed to help with debugging.
      The above configuration would yield the following<br>
    </p>
    <pre>
$ sudo herd command-line podman-networks
/run/current-system/profile/bin/podman network create monitoring

$ sudo herd command-line podman-volumes
/run/current-system/profile/bin/podman volume create grafana

$ sudo herd doc podman-grafana list-actions

command-line:
  Prints podman-grafana's OCI runtime command line invokation.

pull:
  Pull podman-grafana's image (docker.io/grafana/grafana:10.1.5).

$ sudo herd command-line podman-grafana
/run/current-system/profile/bin/podman run --rm --name podman-grafana --network host -v grafana:/var/lib/grafana -v /gnu/store/yqfvhvf8j4008ykr52zh2dmc1d2mjxih-grafana.ini:/opt/bitnami/grafana/conf/grafana.ini docker.io/bitnami/grafana:10.1.5
</pre>
    <p><br>
    </p>
    <p>At last the Scheme API is thought to facilitate the
      implementation of a Guix Home service in the future (you can find
      an untested version of that at [0]). I tested my changes with:<br>
    </p>
    <pre>guix shell -D guix -CPW -- make check-system TESTS="oci-service-docker oci-container oci-service-rootless-podman docker docker-system rootless-podman"</pre>
    <p><br>
    </p>
    <p>Please let me know your thoughts about this!<br>
      <br>
      Thank you for all your work,<br>
      <br>
      giacomo<br>
    </p>
    <p><br>
    </p>
    <p>[0]:
<a class="moz-txt-link-freetext" href="https://github.com/fishinthecalculator/gocix/blob/main/modules/oci/home/services/containers.scm">https://github.com/fishinthecalculator/gocix/blob/main/modules/oci/home/services/containers.scm</a><br>
    </p>
  </body>
</html>

--------------4oR3n2evZzpt5qDyvI400v3d--




Acknowledgement sent to paul <goodoldpaul@HIDDEN>:
New bug report received and forwarded. Copy sent to guix-patches@HIDDEN. Full text available.
Report forwarded to guix-patches@HIDDEN:
bug#76081; Package guix-patches. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Sat, 17 May 2025 15:30:02 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.