node.jscontainerssystemdpodmanquadlet

Podman Quadlet replicate using template files


I'm experimenting with Podman Quadlets and trying to scale an app using a Quadlet template. However, I'm running into an issue with the port format when using a dynamic variable %i in the PublishPort field.

After running a dry run with podman-system-generator --user --dryrun, I get the following error:

quadlet-generator[24155]: Loading source unit file /home/filip/.config/containers/systemd/node-app@.container
quadlet-generator[24155]: converting "node-app@.container": invalid port format '300%i'

Quadlet file node-app@.container:

[Service]
Restart=always

[Container]
ContainerName=node-app-%i
Image=localhost/node-app:latest
Environment=NODE_ENV=production
PublishPort=300%i:3000

I plan to scale the instances like this:

systemctl --user enable --now node-app@1
systemctl --user enable --now node-app@2
systemctl --user enable --now node-app@3

Containerfile:

FROM node:22-slim

WORKDIR /app

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

Simple app example server.js:

const http = require("node:http")

const hostname = "0.0.0.0";
const port = 3000;

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/plain");
    res.end("Hello World\n");
});

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

Is there a correct way to dynamically assign ports in Quadlet using something like $i in the PublishPort field, or is there another approach I should take to achieve this?


Solution

  • Podman v5.3

    Since version v5.3.0-rc2 implemented the PublishPort key in Quadlet .container and .pod files can now accept variables in its value.

    I've updated quadlets and app

    [Service]
    Restart=always
    
    [Container]
    ContainerName=node-app-%i
    Image=localhost/node-app:latest
    Environment=NODE_ENV=production
    Environment=PORT=300%i
    PublishPort=300%i:300%i
    
    const http = require("node:http")
    
    const hostname = "0.0.0.0";
    const port = process.env.PORT || 3000;
    
    const server = http.createServer((req, res) => {
        res.statusCode = 200;
        res.setHeader("Content-Type", "text/plain");
        res.end("Hello World\n");
    });
    
    server.listen(port, hostname, () => {
        console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    Now I can run

    $ systemctl --user start node-app@1
    $ systemctl --user start node-app@2
    $ podman ps
    CONTAINER ID  IMAGE                      COMMAND               CREATED        STATUS        PORTS                   NAMES
    368c4f8e1a6c  localhost/node-app:latest  node --no-warning...  2 minutes ago  Up 2 minutes  0.0.0.0:3001->3001/tcp  node-app-1
    a8c69395e4de  localhost/node-app:latest  node --no-warning...  1 second ago   Up 2 seconds  0.0.0.0:3002->3002/tcp  node-app-2
    
    $ curl localhost:3001
    Hello World
    $ curl localhost:3002
    Hello World