Enrico's blog

This post is part of a series about trying to setup a gitlab runner based on systemd-nspawn. I published the polished result as nspawn-runner on GitHub.

gitlab-runner supports adding extra arguments to the custom scripts, and I can take advantage of that to pack all the various scripts that I prototyped so far into an all-in-one nspawn-runner command:

usage: nspawn-runner [-h] [-v] [--debug]

Manage systemd-nspawn machines for CI runs.

positional arguments:
                        sub-command help
    chroot-create       create a chroot that serves as a base for ephemeral
    chroot-login        enter the chroot to perform maintenance
    prepare             start an ephemeral system for a CI run
    run                 run a command inside a CI machine
    cleanup             cleanup a CI machine after it's run
    gitlab-config       configuration step for gitlab-runner
    toml                output the toml configuration for the custom runner

optional arguments:
  -h, --help            show this help message and exit
  -v, --verbose         verbose output
  --debug               verbose output

chroot maintenance

chroot-create and chroot-login are similar to what pbuilder, cowbuilder, schroot, debspawn and similar tools do.

They only take a chroot name, and default the rest of paths to where nspawn-runner expects things to be under /var/lib/nspawn-runner.

gitlab-runner setup

nspawn-runner toml <chroot-name> outputs a snippet to add to /etc/gitlab-runner/config.toml to configure the CI.

For example:`

$ ./nspawn-runner toml buster
  executor = "custom"
  builds_dir = "/var/lib/nspawn-runner/.build"
  cache_dir = "/var/lib/nspawn-runner/.cache"
    config_exec = "/home/enrico/…/nspawn-runner/nspawn-runner"
    config_args = ["gitlab-config"]
    config_exec_timeout = 200
    prepare_exec = "/home/enrico/…/nspawn-runner/nspawn-runner"
    prepare_args = ["prepare", "buster"]
    prepare_exec_timeout = 200
    run_exec = "/home/enrico/dev/nspawn-runner/nspawn-runner"
    run_args = ["run"]
    cleanup_exec = "/home/enrico/…/nspawn-runner/nspawn-runner"
    cleanup_args = ["cleanup"]
    cleanup_exec_timeout = 200
    graceful_kill_timeout = 200
    force_kill_timeout = 200

One needs to remember to set url and token, and the runner is configured.

The end, for now

This is it, it works! Time will tell what issues or ideas will come up: for now, it's a pretty decent first version.

The various prepare, run, cleanup steps are generic enough that they can be used outside of gitlab-runner: feel free to build on them, and drop me a note if you find this useful!

Updated: Issues noticed so far, that could go into a new version:

  • updating the master chroot would disturb the running CI jobs that use it. Using nspawn's btrfs-specfic features would prevent this problem, and possibly simplify the implementation even more.

This post is part of a series about trying to setup a gitlab runner based on systemd-nspawn. I published the polished result as nspawn-runner on GitHub.

The plan

Back to custom runners, here's my plan:

  • config can be a noop
  • prepare starts the nspawn machine
  • run runs scripts with machinectl shell
  • cleanup runs machinectl stop

The scripts

Here are the scripts based on Federico's work:

base.sh with definitions sourced by all scripts:


config.sh doing nothing:

exit 0

prepare.sh starting the machine:


source $(dirname "$0")/base.sh
set -eo pipefail

# trap errors as a CI system failure

logger "gitlab CI: preparing $MACHINE"

mkdir -p $OVERLAY

systemd-run \
  -p 'KillMode=mixed' \
  -p 'Type=notify' \
  -p 'RestartForceExitStatus=133' \
  -p 'SuccessExitStatus=133' \
  -p 'Slice=machine.slice' \
  -p 'Delegate=yes' \
  -p 'TasksMax=16384' \
  -p 'WatchdogSec=3min' \
  systemd-nspawn --quiet -D $ROOTFS \
    --machine="$MACHINE" --boot --notify-ready=yes

run.sh running the provided scripts in the machine:

logger "gitlab CI: running $@"
source $(dirname "$0")/base.sh

set -eo pipefail

systemd-run --quiet --pipe --wait --machine="$MACHINE" /bin/bash < "$1"

cleanup.sh stopping the machine and removing the writable overlay directory:

logger "gitlab CI: cleanup $@"
source $(dirname "$0")/base.sh

machinectl stop "$MACHINE"
rm -rf $OVERLAY

Trying out the plan

I tried a manual invocation of gitlab-runner, and it worked perfectly:

# mkdir /var/lib/gitlab-runner-custom-chroots/build/
# mkdir /var/lib/gitlab-runner-custom-chroots/cache/
# gitlab-runner exec custom \
    --builds-dir /var/lib/gitlab-runner-custom-chroots/build/ \
    --cache-dir /var/lib/gitlab-runner-custom-chroots/cache/ \
    --custom-config-exec /var/lib/gitlab-runner-custom-chroots/config.sh \
    --custom-prepare-exec /var/lib/gitlab-runner-custom-chroots/prepare.sh \
    --custom-run-exec /var/lib/gitlab-runner-custom-chroots/run.sh \
    --custom-cleanup-exec /var/lib/gitlab-runner-custom-chroots/cleanup.sh \
Runtime platform                                    arch=amd64 os=linux pid=18662 revision=775dd39d version=13.8.0
Running with gitlab-runner 13.8.0 (775dd39d)
Preparing the "custom" executor
Using Custom executor...
Running as unit: run-r1be98e274224456184cbdefc0690bc71.service
executor not supported                              job=1 project=0 referee=metrics
Preparing environment

Getting source from Git repository

Executing "step_script" stage of the job script
WARNING: Starting with version 14.0 the 'build_script' stage will be replaced with 'step_script': https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26426

Job succeeded


The remaining step is to deploy all this in /etc/gitlab-runner/config.toml:

concurrent = 1
check_interval = 0

  session_timeout = 1800

  name = "nspawn runner"
  url = "http://gitlab.siweb.local/"
  token = "…"
  executor = "custom"
  builds_dir = "/var/lib/gitlab-runner-custom-chroots/build/"
  cache_dir = "/var/lib/gitlab-runner-custom-chroots/cache/"
    config_exec = "/var/lib/gitlab-runner-custom-chroots/config.sh"
    config_exec_timeout = 200
    prepare_exec = "/var/lib/gitlab-runner-custom-chroots/prepare.sh"
    prepare_exec_timeout = 200
    run_exec = "/var/lib/gitlab-runner-custom-chroots/run.sh"
    cleanup_exec = "/var/lib/gitlab-runner-custom-chroots/cleanup.sh"
    cleanup_exec_timeout = 200
    graceful_kill_timeout = 200
    force_kill_timeout = 200

Next steps

My next step will be polishing all this in a way that makes deploying and maintaining a runner configuration easy.

This post is part of a series about trying to setup a gitlab runner based on systemd-nspawn. I published the polished result as nspawn-runner on GitHub.

Here I try to figure out possible ways of invoking nspawn for the prepare, run, and cleanup steps of gitlab custom runners. The results might be useful invocations beyond Gitlab's scope of application.

I begin with a chroot which will be the base for our build environments:

debootstrap --variant=minbase --include=git,build-essential buster workdir

Fully ephemeral nspawn

This would be fantastic: set up a reusable chroot, mount readonly, run the CI in a working directory mounted on tmpfs. It sets up quickly, it cleans up after itself, and it would make prepare and cleanup noops:

mkdir workdir/var/lib/gitlab-runner
systemd-nspawn --read-only --directory workdir --tmpfs /var/lib/gitlab-runner "$@"

However, run gets run multiple times, so I need the side effects of run to persist inside the chroot between runs.

Also, if the CI uses a large amount of disk space, tmpfs may get into trouble.

nspawn with overlay

Federico used --overlay to keep the base chroot readonly while allowing persistent writes on a temporary directory on the filesystem.

Note that using --overlay requires systemd and systemd-container from buster-backports because of systemd bug #3847.


mkdir -p tmp-overlay
systemd-nspawn --quiet -D workdir \

I can run this twice, and changes in the file system will persist between systemd-nspawn executions. Great! However, any process will be killed at the end of each execution.


I can give a name to systemd-nspawn invocations using --machine, and it allows me to run multiple commands during the machine lifespan using machinectl and systemd-run.

In theory machinectl can also fully manage chroots and disk images in /var/lib/machines, but I haven't found a way with machinectl to start multiple machines sharing the same underlying chroot.

It's ok, though: I managed to do that with systemd-nspawn invocations.

I can use the --machine=name argument to systemd-nspawn to make it visible to machinectl. I can use the --boot argument to systemd-nspawn to start enough infrastructure inside the container to allow machinectl to interact with it.

This gives me any number of persistent and named running systems, that share the same underlying chroot, and can cleanup after themselves. I can run commands in any of those systems as I like, and their side effects persist until a system is stopped.

The chroot needs systemd and dbus for machinectl to be able to interact with it:

debootstrap --variant=minbase --include=git,systemd,systemd,build-essential buster workdir

Let's boot the machine:

mkdir -p overlay
systemd-nspawn --quiet -D workdir \
    --machine=test --boot

Let's try machinectl:

# machinectl list
test    container systemd-nspawn debian 10      -

1 machines listed.
# machinectl shell --quiet test /bin/ls -la /
total 60

To run commands, rather than machinectl shell, I need to use systemd-run --wait --pipe --machine=name, otherwise machined won't forward the exit code. The result however is pretty good, with working stdin/stdout/stderr redirection and forwarded exit code.

Good, I'm getting somewhere.

The terminal where I ran systemd-nspawn is currently showing a nice getty for the booted system, which is cute, and not what I want for the setup process of a CI.

Spawning machines without needing a terminal

machinectl uses /lib/systemd/system/systemd-nspawn@.service to start machines. I suppose there's limited magic in there: start systemd-nspawn as a service, use --machine to give it a name, and machinectl manages it as if it started it itself.

What if, instead of installing a unit file for each CI run, I try to do the same thing with systemd-run?

systemd-run \
  -p 'KillMode=mixed' \
  -p 'Type=notify' \
  -p 'RestartForceExitStatus=133' \
  -p 'SuccessExitStatus=133' \
  -p 'Slice=machine.slice' \
  -p 'Delegate=yes' \
  -p 'TasksMax=16384' \
  -p 'WatchdogSec=3min' \
  systemd-nspawn --quiet -D `pwd`/workdir \
    --machine=test --boot

It works! I can interact with it using machinectl, and fine tune DevicePolicy as needed to lock CI machines down.

This setup has a race condition where if I try to run a command inside the machine in the short time window before the machine has finished booting, it fails:

# systemd-run […] systemd-nspawn […] ; machinectl --quiet shell test /bin/ls -la /
Failed to get shell PTY: Protocol error
# machinectl shell test /bin/ls -la /
Connected to machine test. Press ^] three times within 1s to exit session.
total 60

systemd-nspawn has the option --notify-ready=yes that solves exactly this problem:

# systemd-run […] systemd-nspawn […] --notify-ready=yes ; machinectl --quiet shell test /bin/ls -la /
Running as unit: run-r5a405754f3b740158b3d9dd5e14ff611.service
total 60

On nspawn's side, I should now have all I need.

Next steps

My next step will be wrapping it all together in a gitlab runner.

This is a first post in a series about trying to setup a gitlab runner based on systemd-nspawn. I published the polished result as nspawn-runner on GitHub.

The goal

I need to setup gitlab runners, and I try to not involve docker in my professional infrastructure if I can avoid it.

Let's try systemd-nspawn. It's widely available and reasonably reliable.

I'm not the first to have this idea: Federico Ceratto made a setup based on custom runners and Josef Kufner one based on ssh runners.

I'd like to skip the complication of ssh, and to expand Federico's version to persist not just filesystem changes but also any other side effect of CI commands. For example, one CI command may bring up a server and the next CI command may want to test interfacing with it.

Understanding gitlab-runner

First step: figuring out gitlab-runner.

Test runs of gitlab-runner

I found that I can run gitlab-runner manually without needing to go through a push to Gitlab. It needs a local git repository with a .gitlab-ci.yml file:

mkdir test
cd test
git init
cat > .gitlab-ci.yml << EOF
  - env | sort
  - pwd
  - ls -la
git add .gitlab-ci.yml
git commit -am "Created a test repo for gitlab-runner"

Then I can go in the repo and test gitlab-runner:

gitlab-runner exec shell tests

It doesn't seem to use /etc/gitlab-runner/config.toml and it needs all the arguments passed to its command line: I used the shell runner for a simple initial test.

Later I'll try to brew a gitlab-runner exec custom invocation that uses nspawn.

Basics of custom runners

A custom runner runs a few scripts to manage the run:

  • config, to allow to override the run configuration outputting JSON data
  • prepare, to prepare the environment
  • run, to run scripts in the environment (might be ran multiple times)
  • cleanup to clean up the environment

run gets at least one argument which is a path to the script to run. The other scripts get no arguments by default.

The runner configuration controls the paths of the scripts to run, and optionally extra arguments to pass to them

Next steps

My next step will be to figure out possible ways of invoking nspawn for the prepare, run, and cleanup scripts.

Some interesting renderings of OpenStreetMap data:

If you want to print out local maps, MyOSMatic is a service to generate maps of cities using OpenStreetMap data. The generated maps are available in PNG, PDF and SVG formats and are ready to be printed.

On a terminal? Sure: MapSCII is a Braille & ASCII world map renderer for the console: telnet mapscii.me and explore the map with the mouse, keyboard arrows, and a and z to zoom, c to switch to block character mode, q to quit.

Alternatively you can fly over it, and you might have to dodge the rare map editing bug, or have fun landing on it: A typo created a 212-story monolith in ‘Microsoft Flight Simulator’

weeklyOSM posts lots of interesting links. Here are some beautiful views of OpenStreetMap edits and renderings:

Durante le feste tutti i giorni di sole erano per combinazione rossi e non si poteva uscire, cosí ho passato le feste in casa a guardare dalla finestra tutto il resto del paese che usciva a godersi il sole.

Ieri era la prima giornata di sole in cui si poteva uscire (giornata gialla rinforzata), e ne ho approfittato per una passeggiata in campagna verso Cinquanta, cercando di avvicinarmi il piú possibile ai confini con San Pietro in Casale e Bentivoglio.

E da oggi, l'avventura arancione ricomincia!

Il giro è stato di circa 12km. Rispetto ai giri precedenti, verso Cinquanta è piú difficile evitare strade asfaltate, e non è praticamente possibile percorrere la linea di confine. Ho cercato comunque di evitare il piú possibile le strade con automobili, e di avvicinarmi il piú possibile al confine.

Anche in questo caso ho aggiunto tutto il percorso su OpenStreetMap in modo che si possa esplorare su una cartina.

Dal Laghetto dei Germani al sottopasso per Cinquanta

Sono partito dal Laghetto dei Germani gentrificato cantando "là dove c'eeera il boschetto e il bar con le crescentine ooora c'èee / un cantiere di soluzioni abitative in classe energeticaA…AAAAApiú"


Sono uscito dal laghetto su via Codini, l'ho presa verso sinistra, e poi ho girato a destra nella prima strada sterrata, seguendola fino al ponte sul Canale Emiliano Romagnolo.

Ho attraversato il ponte e ho girato a destra, non seguendo l'argine del canale ma l'altra strada che va sempre verso la Provinciale. Con quella sono arrivato esattamente al nuovo semaforo pedonale sulla Provinciale.

Il semaforo di solito è giallo lampeggiante, ma ha un bottone con scritto "PREMERE - PUSH". Ho provato a premerlo e aspettare un po', ma è rimasto giallo lampeggiante. Allora ho provato a pusharlo in inglese, ma è rimasto comunque giallo lampeggiante. Si vede che anche il semaforo era giallo rinforzato!

Ho attraversato la provinciale con molta cautela e sono sceso nel sottopasso verso Cinquanta.


Dal sottopasso al confine con San Pietro in Casale

Appena salito dal sottopasso, c'è una stradina sulla sinistra verso i civici 4/1 - 4/5. L'ho presa e l'ho seguita costeggiando tutta la bella villa, e ho voltato a destra passata la villa.

La strada che prima è asfaltata, poi diventa una cavedagna che continua fino a via Casale. Ci sono un paio di piccoli fossettini da saltare, ma sono veramente piccoli.

Ho preso via Casale a sinistra verso Rubizzano, e poi ho voltato di nuovo a sinistra per via Pizzardi.

Ho seguito via Pizzardi fino in fondo. A un certo punto volta a destra e si ricongiunge con via Casale. Lí c'è il confine con San Pietro, con tanto di cippo e cartello, ma niente guardiola perché era solo una giornata gialla. Da oggi me lo immagino cosí.


Dal confine con San Pietro al confine con Bentivoglio

Ho seguito via Casale fino a via Cinquanta, e poi ho voltato a sinistra verso la chiesa. Sulla sinistra lungo la strada c'è un piccolo palo con un disegno dell'arcobaleno con scritta "Andrà tutto bene" sbiaditissimo che dà molto da pensare.

Arrivato alla chiesa ho voltato a destra per via Chiesa, seguendola fino all'incrocio con via Codronchi. Ho voltato a destra in via Codronchi, e sono salito sul ponte sul Canale.

Appena passato il canale, ho voltato a sinistra sull'argine e l'ho seguito fino a via Marconi.


Camminando sull'argine però ho dovuto attraversare un pezzetto del territorio di Bentivoglio. Per fare una cosa fatta meglio, si può provare a camminare sotto l'argine del canale, e arrivare su via Marconi seguendo poi l'argine della Calcarata a destra.


Ritorno a San Giorgio

Arrivato su via Marconi, ho seguito la comoda ciclabile in sede separata fino a tornare a San Giorgio, chiudendo il giro.

COVID-19 vaccination has started, and this site tracks progress in Italy. This site, world-wide.

Reverse Engineering the source code of the BioNTech/Pfizer SARS-CoV-2 Vaccine has a pretty good description of the BioNTech/Pfizer SARS-CoV-2 Vaccine, codon by codon, broken down in a way that I managed to follow.

From the same author, DNA seen through the eyes of a coder

Continua il periodo arancione, e continua anche la mia esplorazione delle possibilità di camminate e biciclettate lungo i confini di San Giorgio di Piano.

Oggi ho fatto un giro di circa 15km costeggiando il confine con Argelato sempre sugli gli argini del canale Riolo. Questo giro potrebbe anche collegarsi con il precedente lungo l'argine di Riolo, per fare un bel giro piú lungo.

Anche in questo caso ho aggiunto tutto il percorso su OpenStreetMap in modo che si possa esplorare su una cartina.

Dal campo sportivo all'argine di Riolo

Partendo dal campo sportivo sono arrivato su via Selvatico, ho voltato a destra e poi subito a sinistra verso l'Area di Riequilibrio Ambientale La Balia.


Il cartello dice "Itinerari da scoprire", andiamo a scoprirli! Appena voltato a sinistra, compare un bel cartello di divieto di transito - proprietà privata, che sospetto non sia stato messo dal comune: che senso ha mettere un cartello verso un'area verde comunale, e poi chiudere la strada subito dopo?


Continuando sempre dritto, a un certo punto la strada svolta a destra verso il portone (e la buchetta della posta!) di casa di qualcuno, ma c'è un sentiero che continua dritto attraverso una specie di boschetto, e arriva all'argine di Riolo.

Dall'argine di Riolo a via Centese

Voltando a sinistra sull'argine e costeggiando sempre Riolo, sono arrivato su via Centese, e ho voltato a sinistra prendendo la ciclabile che torna verso San Giorgio.

Si potrebbe continuare sull'argine di Riolo, ma in quel punto diventa territorio di Argelato. Chi ha permesso che Argelato si prendesse quel territorio? Il Montuni avrebbe potuto essere nostro!

La ciclabile passa davanti alla Cantina Sociale di Argelato e al banco carni di Golinelli, ma una crudele linea di confine mi ha impedito di mettere le mani su quei meravigliosi ciccioli freschi.


Da via Centese a Stiatico

Ho voltato a destra su via Stiatico e poi a destra di nuovo sulla prima strada di campagna che ho trovato appena finiti i capannoni.

Qui ho sbagliato a consultare la mappa: avrei dovuto prendere invece la seconda, e ho accidentalmente sconfinato nel territorio di Argelato.

Seguendo la strada sono arrivato di nuovo sull'argine di Riolo, e ho voltato a sinistra, con l'intenzione di seguirlo fino contro al Boscovivo di Argelato.

Invece, arrivato su via Casadio, l'argine si interrompe perché qualcuno l'ha recintato come parte di casa sua.


Non ho preso l'altro argine per non sconfinare di nuovo in Argelato, anche se sarebbe probabilmente un bel giro continuare, costeggiare Boscovivo, e tornare per via Funo verso le Larghe.

Ho ripiegato invece su via Casadio, arrivando a Stiatico.

Da Stiatico a San Giorgio di Piano

Arrivato a Stiatico ho voltato a destra verso le Larghe di Funo, e poi a sinistra in via de' Giudei, fino ad arrivare alla ciclabile che costeggia la via Provinciale.

Voltando a sinistra sulla ciclabile, l'ho seguita fino a tornare a San Giorgio, apprezzando la nostra fiorente piccola e media impresa e l'imponente mausoleo del Mercatone Uno, chiudendo il giro.

Per celebrare il periodo arancione sto esplorando le possibilità di camminate e biciclettate lungo i confini di San Giorgio di Piano.

Oggi ho fatto un giro di circa 12km costeggiando il confine con Castello d'Argile/Mascarino, tutto sugli gli argini del canale Riolo e del Canale Emiliano Romagnolo.

Ho aggiunto tutto il percorso su OpenStreetMap in modo che si possa esplorare su una cartina. Per chi non conosce OpenStreetMap, la differenza con altri servizi è che se su OpenStreetMap manca qualcosa lo puoi sistemare. Se su altri servizi manca qualcosa, ti attacchi al tram e tiri forte. È anche possibile creare siti e cartine liberamente usando i dati di OpenStreetMap, mentre con altri servizi vale il suddetto discorso del tram.

Anni fa provai anche a chiedere aiuto al comune e all'Unione Reno Galliera per cercare di tirar fuori una cartina per passeggiate in mezzo alla campagna su percorsi che non fossero proprietà private, ma mi sorrisero, e mi trattarono con quell'educazione e cortesia estreme che generalmente la gente usa coi matti.

In mancanza di fonti istituzionali, per l'accessibilità di questo percorso mi sono rifatto a due regole indicative: gli argini dei canali dovrebbero essere terreno del demanio, e le strade che arrivano fino alle buchette della posta di casa di qualcuno dovrebbero essere percorribili.

Da via Don Minzoni all'argine di Riolo

Partendo da via don Minzoni, sono entrato nella cavedagna che attraversa il podere di Atti, ringraziando il signore (quello che lavora all'urbanistica) che non abbiano ancora asfaltato tutto per farci in quartierino di muri grigi chiamato Green qualcosa. Ho visto però che ci stanno lavorando.

Ho seguito la cavedagna, attraversando il fosso con la traversina di ferrovia e via Selvatico, e poi sempre dritto fino a Riolo.

Dall'ultima buchetta della posta fino a Riolo c'è un piccolo tratto tra i frutteti, che è bellissimo in primavera, ma potrebbe essere proprietà privata. Io nel dubbio, se vedo qualcuno, chiedo il permesso di attraversarlo e trovo sempre persone gentili.

L'argine di Riolo

Arrivato a Riolo l'ho preso verso a destra. Avrei potuto attraversare il ponte della Scodellara, perché di là sarebbe ancora un lembo di San Giorgio, però poi dopo poco l'altra riva di Riolo passa sotto Argile, e quindi niente.

Una volta da queste parti le persone xenofobe dicevano "Sèra l'oss ch'a i'é i pivén ch'i vinen zò da Mascarén". Ora abbiamo chiuso i confini anche con Mascarino, ma le persone xenofobe continuano a lamentarsi.


La camminata sull'argine è bella e lunga. Attraversando via Mascherino l'argine continua e continua fino al Canale Emiliano Romagnolo. Poco prima del canale c'è un ponticello un po' disastrato e stretto.


Sono arrivato fino al punto in cui Riolo scorre sotto al Canale Emiliano Romagnolo, ho ammirato l'ingegnosa opera idraulica, e sono salito sull'argine del Canale.

L'argine del Canale Emiliano Romagnolo


Salito sul canale l'ho trovato vuoto. Dice che l'acqua sia ferma ai confini del Piemonte e stiano cercando di capire se serva o no l'autocertificazione. Per me è stata un'ottima occasione per vedere com'è fatta un'idrovora anche da sotto.

Se l'argine di Riolo è normalmente ben tenuto, quello del canale è un po' piú selvaggio, soprattutto all'inizio, ed ero contento di avere scarpe buone e pantaloni lunghi.

Proseguendo ho incontrato uno che è finito col camion in mezzo al canale, ma stava bene.


Prima di arrivare al ponte con la provinciale, subito dopo una villa c'è un sentiero sulla destra che riporta su via Codini. Sono rientrato attraversando il Laghetto dei Germani gentrificato, e poi via Irma Bandiera, chiudendo il giro.