Author Archives: Lorenzo Bettini

About Lorenzo Bettini

Lorenzo Bettini is an Associate Professor in Computer Science at the Dipartimento di Statistica, Informatica, Applicazioni "Giuseppe Parenti", Università di Firenze, Italy. Previously, he was a researcher in Computer Science at Dipartimento di Informatica, Università di Torino, Italy. He has a Masters Degree summa cum laude in Computer Science (Università di Firenze) and a PhD in "Logics and Theoretical Computer Science" (Università di Siena). His research interests cover design, theory, and the implementation of statically typed programming languages and Domain Specific Languages. He is also the author of about 90 research papers published in international conferences and international journals.

Ansible, Molecule, Docker and GitHub Actions


  • 19 February 2023: exclude ansible-lint problems on tests/test.yml
  • 27 April 2023: updated the molecule docker plugin

Last year, I got familiar with Ansible, the automation platform I now use to install and configure my Linux installations. I must thank Jeff Geerling and his excellent book “Ansible for DevOps“, which I highly recommend!

I have already started blogging about Ansible and its testing framework, Molecule. However, in the first blog post, I used Ansible and Molecule to demonstrate Gitpod with a minimal example.

In this blog post, I’d like to document the use of Ansible and Molecule with a slightly more advanced example and how to test an Ansible role against 3 main Linux distributions, Fedora, Ubuntu, and Arch. To test the Ansible role, we will use Molecule and Docker. Finally, I’ll show how to implement a continuous integration process with GitHub Actions. The example consists of a role for installing zsh, setting it as the user’s default shell, and creating an initial “.zshrc” file. It will be a long post because it will be step-by-step.

The source code used in this tutorial can be found here: The GitHub repository is configured to be used with Gitpod (see my other blog post concerning using the online IDE Gitpod).

Install ansible and molecule

I’m assuming Docker, Python, and Pip are already installed.

First, let’s install Ansible and Molecule (with Docker support). We’ll use pip to install these tools. This method works on all distributions since it’s independent of the ansible and molecule packages provided by the distribution (Ubuntu does not even provide a package for molecule):

This will install ansible and molecule in “$HOME/.local/bin,” so this path must be in your PATH (it should already be the case in most distributions).

Create the role

This is the command to initialize a role with the directories and files also for molecule (with docker):

In this example, I’ll run:

This is the resulting directory structure of the created project (note that, at the time of writing, the official guide,, is not updated with the directory and file structure):

What I do next is to enter the directory, remove “.travis.yml” (since we want to build on GitHub Actions), and create a Git repository (with “git init”). I’m also pushing to GitHub.

First, let’s adjust the file meta/main.yml with the information about this role and author:

The role’s name should be the same as the one specified in the “init” command (I don’t know why this file has not been generated with the role_name already set). Otherwise, the other generated files for Molecule will not work.

The role’s main tasks are defined in tasks/main.yml. Currently, the generated file does not execute any task.

Manual tests

The “init” command also created a tests directory to manually and locally test the role. We are interested in automatically testing the role. However, since the role is currently empty, it is safe to try to run it against our own machine. At least, we can check that the syntax of the role is OK, and we can perform a “dry-run” without modifying anything on our machine.

The current contents of the files generated in the “tests” directory will not work out of the box.

First, the tests/test.yml playbook:

Correctly refers to our role, but ansible will not be able to find the role in the default search path (because the role is the project’s path).

We can change the role reference with a relative path (the use of a relative path will require a few configurations to make linting happy, as we will see later):

Then, we can try to run it, checking the syntax and doing a “dry-run”:

The “dry-run” (–check) fails because, on my machine, there’s no SSH server, and by default, the tests/inventory file (specifying “localhost”) would imply an SSH connection:

To avoid SSH, we can change the file as follows:

Let’s try again with the “–check” argument, and now it works.

Run the complete Molecule default scenario

The “init” command created a default Molecule scenario in the file default/molecule.yml:

As we can see from this file, the Docker image used by Molecule is centos:stream8. For the moment, we’ll stick with this image.

Molecule will execute a playbook against a Docker container of this Docker image. We’re implementing a role, not a playbook. The playbook is defined in the file default/converge.yml:

In fact, “converge” is the action of performing the playbook against the Docker image, the “instance”. As you see, the “init” command generated this file automatically based on the role that we created.

There’s also a default/verify.yml file that is used to verify that some expected conditions are true once we run the playbook against the Docker instance. We’ll get back to this file later to write our own assertions. The contents of this generated file are as follows (the assertion is always verified):

To check that the scenario already works, we can run it end-to-end with the command “molecule test” issued from the project’s root. Remember that Molecule will download the Docker image during the first run, which takes time, depending on your Internet connection. This is the simplified output:

As reported in the first line, this is the entire lifecycle sequence:

dependency, lint, cleanup, destroy, syntax, create, prepare, converge, idempotence, side_effect, verify, cleanup, destroy

Thus running the entire scenario always implies starting from scratch, that is, from a brand new Docker container (of course, the pulled image will be reused). Note that after “converge,” the scenario checks “idempotence,” which is a desired property of Ansible roles and playbooks. After verification, the Docker instance is also destroyed. Of course, if any of these actions fail, the lifecycle stops with failure.

Setup the CI on GitHub Actions

Our role doesn’t do anything yet, but we verified that we could run the complete Molecule scenario. Before going on, let’s set up the GitHub Actions CI workflow. We’ll use the Ubuntu runner, where Docker and Python are already installed. We’ll have first to install ansible and molecule with pip, and then we run the “molecule test”.

Concerning the pip installation step, I created the file pip/requirements.txt in the project with these contents (they correspond to the pip packages we installed on our machine):

Then, I create the file .github/workflows/molecule-ci.yml with these contents:

Now that our CI is in place, GitHub Actions will run the complete Molecule test scenario at each pushed commit. The environment variables at the end of the file will allow for colors in the GitHub Actions build output:

Familiarize with Molecule commands

While implementing our role, we could run single Molecule commands instead of the whole scenario (which, in any case, will be executed by the CI).

With “molecule create,” we create the Docker instance. Unless we run “molecule destroy” (which is executed by the entire scenario at the beginning), the Docker container will stay on our machine. Once the instance is created, you can enter the container with “molecule login“. This is useful to inspect the state of the container after running the playbook (with “molecule converge“) or to run a few commands before writing the tasks for our role:

The “login” command is more straightforward than running a “docker” command to enter the container (you don’t need to know its name). Remember that unless you run “molecule destroy,” you’ll find the same state if you exit the container and back in.

Once you run “molecule converge“, you can run “molecule verify” to check that the assertions hold.

To get rid of the instance, just run “molecule destroy“.

Let’s start implementing our role’s tasks

To start experimenting with Molecule for testing Ansible roles, the official Fedora Docker image is probably the easiest. In fact, such an image comes with “python” already installed (and that’s required to run Ansible playbooks). Moreover, it also contains “sudo”, another command typically used in Ansible tasks (when using “become: yes”).

Thus, let’s change the image in the file default/molecule.yml:

You can commit, push, and let GitHub Actions verify that everything is still OK.

Now it’s time to edit the primary role’s file, tasks/main.yml. Let’s add the task to install ZSH. In this example, I’m using “ansible.builtin.package module – Generic OS package manager” so that we are independent of the target OS. This is useful later because we want to test our role against different Linux distributions. This Ansible module is less powerful than the specific package manager modules, but for our goals, it is sufficient. Moreover, in the Linux distributions that we’ll test, the name of the package for ZSH is always the same, “zsh”.

If we had already created the instance, we first need to run “molecule destroy” to avoid errors due to the previous Docker container.

Let’s run “molecule converge“. If you don’t have the “fedora:36” Docker image already in your cache, this command will take some time the first time. Moreover, also the task of installing the “zsh” package might take some time since the package must be downloaded from the Internet, not to mention that dnf is not the fastest package manager on earth. In fact, the Ansible package module will use the distribution package manager, that is, dnf in Fedora. Here’s the output:

Let’s enter the container with “molecule login“. Now, zsh should be installed in the container:

Of course, you could always run the entire “molecule test,” but that takes more time, and for the moment, we don’t have anything to verify yet. The idempotency of the Anslibe package module implies Idempotency.

Change the user’s shell and verify it

Now, we want to change the user’s shell to zsh, and we will verify it. Let’s follow a Test-Driven Development approach, which I’m a big fan of. We first write the verification tasks in verify.yml, make sure that “molecule verify” fails, and then implement the task in our role to make the test succeed.

First, how to get the user’s shell? In the Docker container, the $SHELL environment variable is not necessarily set, so we directly inspect the contents of the file “/etc/passwd” and some shell commands to get the user’s current shell. To write the shell commands, we can enter the container (molecule login), assuming we have already created the instance, and perform some experiments there. Remember that when we’re inside the container, we are “root”, so in our experiments, we’ll try to get the root’s shell.

So, we have our shell piped command to get the root’s shell:

In verify.yml, we want to get the shell of the user executing Ansible. In our molecule tests, it will be root, but the user will be different in the general use case. Thus, we use Ansible’s fact “ansible_user_id”:

Then, we’ll compare it against the desired value, NOT “/bin/bash”, but “/bin/zsh”. Note that, by default, the generated molecule/verify.yml has “gather_facts: false”. We need to remove or set that line to true so that Ansible populates the variable with the current user. Here are the contents (we must use the module “shell” and not “command” because we need the “|”):

Since we have already created the instance and converged that, let’s run “molecule verify“:

As expected, it fails.

Let’s add the task in our role to set the current user’s shell to zsh (we rely on the Ansible user module):

Let’s run “molecule converge” (since we had already converged before adding this task, the installation of zsh does not change anything):

And let’s run “molecule verify“, and this time it succeeds!

As usual, we commit, push, and let the CI run the whole scenario.

The verification would not be really required since we should rely on the correctness of the Ansible user module). However, I thought this could be the moment to experiment with Molecule verification.

Note that if you enter the container, the “/etc/passwd” has been modified, but you’re still on bash. That’s because the change becomes effective when you log out and log in as a user. In a Docker container, that’s not possible, as far as I know. However, since log out and login are expected in a real system, as long as the shell is modified in “/etc/passwd”, we’re fine.

Add the file .zshrc

Since we want to set up zsh for the user, we should also add to the converged system a “.zshrc” with some reasonable defaults. For example, if you enter the container and run zsh, you’ll see that you have no command history. The history should be enabled in the file “.zshrc”.

Files are searched for in the directory “files” of the project, which the “init” command created for us. I had an existing small “.zshrc” with the enabled history, command completion, and a few aliases:

I put such a file in files/zshrc (I prefer not to have hidden source files, so I removed the “.”). In the role, I added this task, which copies the source file into the converged system in the current user’s home directory with the name “.zshrc”:

Of course, the “copy module” is idempotent and performs the action only if the source and the target files differ.

Let’s converge, enter the Docker container and run “zsh”. Now, the command history works.


Let’s enable linting to the Molecule scenario. Remember that the scenario has an initial phase for linting.

First of all, we have to install the two additional pip packages, yamllint, and ansible-lint. In our system, we run the following:

Of course, for the CI, we have to update pip/requirements.txt accordingly, adding these two packages.

Then, we have to enable the “lint:” section in default/molecule.yml:

Before going on, let’s exclude our “tests/test.yml” file from ansible-lint: as mentioned above, using a relative path will make ansible-lint complain. However, for that simple test file we don’t care. So, let’s create a file “.ansible-lint” in the root directory with these contents:

Now we can run “molecule lint“:

The output also suggests how to skip some of these issues. However, let’s try to fix these problems.

Concerning the meta/main.yml this modified version fixes the reported issues (we’ll deal with Ubuntu and Arch later):

Then, we adjust the other two files, “converge.yml” and “verify.yml”. These are the relevant changed parts, respectively:

Now, “molecule lint” should be happy.

Commit and push. Everything should work on GitHub Actions.

Note: when we created the project with “molecule init”, the command also created a “.yamllint” configuration file in the root:

This configuration file enables and disables linting rules. This is out of the scope of this post. However, to experiment a bit, if we remove the last line “truthy: disable” and run “molecule lint,” we get new linting violations:

Because “become: yes” should be changed to “become: true”. I guess it’s a matter of taste whether to enable such a linting rule or not. I’ve seen many examples of Ansible files with “become: yes”. After fixing “main.yml”, there is still a warning (not an error) on the YAML file of our GitHub Actions in correspondence with the “on” line:

You can find a few issues on this being considered false positive or not. A simple workaround is to add a comment in the “molecule-ci.yml” file to make yamllint skip that:

Testing with Ubuntu, the “prepare” step

Let’s say that, besides “fedora:36“, we also want to test our role against the Docker image “ubuntu:jammy“. Instead of creating a new scenario (i.e., another directory inside the directory “molecule”), let’s parameterize the molecule.yml with an environment variable, e.g., MOLECULE_DISTRO, which defaults to “fedora:36”, but that can be passed on the command line with a different value. This is the interesting part:

Nothing changes if we run molecule commands as we did before: we still use the Fedora Docker image. If we want to try with another image, like “ubuntu:jammy”, we prefix the molecule command with that value for our environment variable.

IMPORTANT: Before trying with another Docker image, make sure you run “molecule destroy” since now we want to use a different Docker image.

Let’s try to converge with the Ubuntu Docker image…

What could go wrong?

As I anticipated, while the Fedora Docker image comes with python preinstalled, the Ubuntu Docker image does not. The latter does not even have “sudo” installed, which is required for running our tasks with “becomes: yes”. The converge failed, but the Ubuntu image has been created so you can enter the Docker container and verify that these packages are not pre-installed.

One could try to add the tasks in the role to install python and sudo (not by using “package” because such Ansible modules require python already installed). However, this would not make sense: our role is meant to be executed against an actual distribution, where these two packages are already installed as base ones. Jeff Geerling provides a few Docker images meant for Ansible, where python and sudo are already installed. However, instead of using his Ubuntu image, let’s explore another Molecule step: prepare.

As documented:

The prepare playbook executes actions which bring the system to a given state prior to converge. It is executed after create, and only once for the duration of the instances life. This can be used to bring instances into a particular state, prior to testing.

So, let’s modify this part in molecule.yml (this modification is not strictly required because if the directory of molecule/default contains a file “prepare.yml,” it will be automatically executed; it might still be good to know how to specify such a file, in case it’s in a different directory or it has a different name):

Now, in molecule/prepare.yml we create the preparation playbook. This is kind of challenging because we cannot rely on Ansible facts nor on most of its modules (remember: they require python, which we want to install in this playbook). We can rely on the “ansible.builtin.raw module – Executes a low-down and dirty command”. And looking at its documentation, we can see that it fits our needs:

This is useful and should only be done in a few cases. A common case is installing python on a system without python installed by default.

So, here’s the prepare.yml playbook:

Of course, we must run this task only when we are using Ubuntu (see the condition). We also specify “changed_when: false” to avoid linting problems (“no-changed-when # Commands should not change things if nothing needs doing.”).

Running “molecule converge” now succeeds (note the “prepare” step):

Of course, also “MOLECULE_DISTRO=ubuntu:jammy molecule verify” should succeed:

But it doesn’t. That’s because the “pipefail” we added to make lint happy works in bash but not in sh, which is used by default in Ubuntu (in Fedora, it was bash). It’s just a matter of adjusting that verification’s task accordingly:

And now verification succeeds in Ubuntu as well.

If you run this against Fedora (remember that you must destroy the Ubuntu instance first), the task “Install python in Ubuntu” will be skipped.

Note that if you run

Let’s try to converge with the Ubuntu Docker image…

and then execute

Molecule will reuse the image just created: it does not recreate the Docker image even if you haven’t specified any environment variable. This means that, as mentioned above, if you want to test with another value of the environment variable (including the default case), you first have to destroy the current image. By defining several scenarios, as we will see in a minute, there’s no such limitation.

Add a GitHub Actions build matrix

Let’s modify the GitHub Actions workflow to test our role with Fedora and Ubuntu in two jobs using a build matrix. These are the relevant parts to change to use the environment variable MOLECULE_DISTRO that we introduced in the previous section:

Now GitHub Actions will execute two jobs for each pushed commit:

Using different scenarios

We now see a different technique to test with different Linux distributions. Instead of using an environment variable to parameterize Molecule, we create another Molecule scenario. To do that, it’s enough to create another subdirectory inside the “molecule” directory. We’ll use the “Ubuntu” example to see this technique in action. (Before doing that, remember to run “molecule destroy” first).

First, let’s undo the modification we did in the file “molecule.yml”:

And let’s create another subdirectory, say “ubuntu”, inside “molecule”, where we create this “molecule.yml” file (it’s basically the same as the one inside “default” where we specify “ubuntu:jammy” and a different name for the “image”):

Let’s copy the “default/verify.yml” and “default/converge.yml” into this new directory, and let’s move the “default/prepare.yml” into this new directory, where we change the contents as follows (that is, we get rid of the “when” condition since this will be used only in this new scenario):

To summarize, this should be the layout of the “molecule” directory (we’ll get rid of duplicated contents in a minute):

Now, running any molecule command will use the “default” scenario. If we want to execute molecule commands against the “ubuntu” scenario, we must use the argument “-s ubuntu” (where “-s” is the short form of the command line argument “–scenario-name”).

For example

So we can converge, verify, and experiment with the two scenarios without destroying a previously created instance.

Of course, we adapt the GitHub Actions workflow accordingly to use scenarios instead of environment variables:

Now, let’s clean up our files to avoid duplications. For example, “verify.yml” and “converge.yml” are duplicated in the “default” and “ubuntu” directories. We take inspiration from the official documentation

Let’s move the shared files “verify.yml” and “converge.yml” to a new subdirectory, say “shared”. So the layout should be as follows:

The last part of both “molecule.yml” files in “default” and “ubuntu” must be changed to refer to files in another directory (note that the “verifier” part has been removed since it’s specified in the “provisioner” part):

Now we reused the common files between the two scenarios. Of course, we verify that everything still works in both scenarios.

Testing with Arch, a custom Dockerfile

Let’s now test this simple role also with Arch Linux. The idea is to create another scenario, e.g., another subdirectory, say “arch”. We could follow the same technique that we used for Ubuntu because also the Arch Docker image has to be “prepared” with “python” and “sudo”. However, to try something different, let’s rely on a custom Docker image specified with a Dockerfile.

The “molecule.yml” in the “arch” directory is as follows:

NOTE: the specification “platform: linux/amd64” is not required because we use a custom Dockerfile. It is required if you want to test this scenario on a Mac m1 (by the way, see my other blog post about Docker on a Mac m1): while Ubuntu and Fedora also provide Docker images for the aarch64 (arm) architecture, Arch Linux does not. So we must force the use of the Intel platform on Arm architectures (of course, on Mac m1, the Docker container will be emulated).

And in the same directory, we create the Dockerfile for our Arch Docker image:

For this example, the Docker image for Arch is simple because we only need “python” and “sudo” to test our role.

Now the directory layout should be as follows:

Now, when Molecule creates the instance, it will use our custom Dockerfile.

We verify that the “arch” scenario also works and update the GitHub Actions workflow by adding “arch” to the scenario matrix.

I hope you find this post helpful in getting started with Ansible and Molecule.

Stay tuned for more posts on Ansible! 🙂

Installing Ubuntu Linux on an Acer Aspire Vero

As I wrote in this other article, I recently bought an Acer Aspire Vero, which I greatly enjoy. Of course, I immediately installed Linux on this machine. In this article, I’ll report my experience installing Ubuntu (Ubuntu 22.10 “Kinetic Kudu”) on this Acer Aspire Vero.

Although nowadays I’m mainly an Arch Linux user, when installing Linux on a brand new laptop, I typically prefer to start with Ubuntu.


I have already downloaded the Ubuntu ISO and copied it into a USB stick with Ventoy.

I need the F12 boot menu to boot from the USB stick. This is also useful later because I typically have several Linux distributions installed on the same computer. To enter the BIOS, you must press F2 while the laptop is booting (when you see the “Acer” logo). Make sure the “F12 Boot Menu” is enabled (by default, it’s disabled):

I also disabled “Secure Boot”. If you go to the BIOS “boot” tab, you see that you cannot change the boot entries.

The official documentation describes the procedure to make those entries changeable: The idea is to (at least temporarily) set a supervisor password (take note of that password!):

This will allow you to change the entry of “Secure Boot”:

Then, you can disable the Supervisor Password (you have to use the password you had previously chosen).

Then, it’s time to prepare some room in the SSD for Linux. I do that by shrinking the Windows partition from Windows itself. After installing a few programs on Windows (and performing the system updates), that’s the amount of used space:

I opened “Disk Management”, selected the primary partition, and used the context menu “Shrink Volume…”; since I’m not planning to use Windows much, 137Gb should be enough for the Windows partition after shrink:

And that’s the result:

Before installing Linux, I also disabled “fast startup” in Windows: this will allow me later to access the Windows partition from Linux (otherwise, the Windows partition would be in an inconsistent state):

OK, let’s reboot with the USB stick (I’m using Ventoy) and press F12 to get to the boot menu to choose to boot from the USB:

Ubuntu boots fine.

I decided first to try Ubuntu and see whether everything works in the live environment:

The sound works. I’d say that WiFi and Bluetooth are also working from the new GNOME 43 menu in the top-right corner  (in fact, I can connect to my WiFi). Moreover, the “Balanced” profile is automatically selected, meaning power profiles also work.


The overall installation process went smoothly and fast.

I prefer to manually partition the disk because I want a swap partition (for hibernation), a standard EXT4 partition mounted on a directory that I will share with other Linux installations, and the root partition as BTRFS.

The installed system

So here we are on the installed system; as usual, I’m greeted by the initial setup dialog:

Wayland works and touchpad gestures work as well.

Grub detected my existing Windows installation so that I could boot Windows from the grub menu.

Usually, I have to increase the font size on my computers. This laptop provides 1920×1080 (16:9) on a 15,6 screen. Typically, I have to use Gnome Tweaks, but in this case, using the “Accessibility” menu and selecting “Large Text” was enough for having a readable screen (this corresponds to a font scaling factor of 1.25):

Power consumption

I selected “Power Saver” as the power profile in the Gnome menu, and I have installed “powertop”. I ran “sudo powertop –auto-tune,” and then I ran “sudo powertop” to see the power consumption without further interacting with the computer:

If I decrease the brightness a bit, it looks even better:

Since 9 hours is the declared time in the computer spec, I’d say Linux works great on this computer in this respect (even better than Windows).

See also later in this article another mechanism to improve power consumption.

Other configurations

I had to perform some additional tweaks, which I had already blogged about:

Finally, I read on the Arch Wiki and in other articles that it’s better to disable the VMD controller in the BIOS to optimize power consumption.

WARNING: if you disable VMD in the BIOS, Windows will refuse to boot. To avoid this problem:

  • boot Windows and configure it to boot in safe mode and reboot;
  • disable VMD in the BIOS (as shown in the following);
  • boot Windows (in safe mode) and reboot Windows in normal mode.

To disable VMD in the BIOS, go to the “Main” section:

Press Ctrl+S to show the advanced hidden entries (including the VMD Controller) and disable it:

After rebooting into Ubuntu, I notice that the fan is almost always off, so maybe disabling VMD does something concerning power consumption.

That’s all!

Linux runs fine on this laptop! 🙂

Stay tuned for other blog posts about other Linux distributions installed on this laptop.

Installing Amarok on Arch Linux

I have always liked Amarok, the (initially) default KDE media player. It’s very feature-rich, nothing compared to Elisa. Moreover, it has two crucial features that I haven’t found in any other players:

  • it saves statistics (play count and stars) directly into the music file
  • it synchronizes statistics with iPod

Unfortunately, while still maintained, you won’t find pre-built packages in mainstream distributions (e.g., Ubuntu). Thus, you must install that from sources, which is problematic. However, for Arch Linux, there’s an AUR package, which takes care of the compilation and, most of all, its dependencies.

In this blog post, I’ll summarize the steps for installing Amarok to access iPods (I still have an iPod classic).

First, you need to install the Phonon backend required by Amarok:

If you want to use Amarok with an iPod, you must first install

IMPORTANT: the iPod library (this must be present when Amarok is compiled from sources; if you forget about that, you’ll need to recompile Amarok, e.g., by specifying “–rebuild” as a command line argument to the AUR helper):

Then, we’re ready to install (i.e., compile from sources) Amarok from the AUR repository (I’m using the “yay” AUR helper here, but if you use another one, use your preferred one):

Now be patient: it will take several minutes for the compilation to finish (about 20 minutes on a decent machine)!

If you’re on KDE, you can now enjoy Amarok.

If you’re on GNOME, there’s still something to fix. In particular, you’ll see Amarok lacks lots of icons:

You need to install Breeze icons:

And now you can also enjoy icons:

Concerning the iPod: first, you have to mount it, and then you start Amarok so that Amarok can see the mounted iPod.

Enjoy your music! 🙂

Docker on macOS M1

Although I’m a Linux user, I also recently bought a Mac Air M1, and I wanted to use Docker (a big part of my TDD book) to ensure that my projects based on Docker work on m1 as well.

I then went to the Docker website for macOS and downloaded the version for the Apple m1 chip:

Then, I continued with the installation:

Let’s start it. Although m1 is fast, starting Docker Desktop takes some time.

Although I’m already familiar with Docker (on Linux), I decided to follow the “getting started” tutorial, which is well done:

At least, I’m sure that Docker is working on this machine.

The desktop app is well done, with a few sections to inspect Images, Containers, etc.

And, of course, there’s the “Preferences” section. For the moment, I stick with the defaults.

From the terminal, I ran the usual “hello-world” image:

I also tried to run a Ubuntu container. Inside the container, I verified that it’s running an “aarch64” version instead of the x86 one (“amd64”).

I also installed “file” to verify that it’s using aarch64 binaries (“arm64”):

From the Desktop application, you can quickly enter a container with a terminal:

Now, it’s time to verify that my Java projects based on Docker work as expected.

Java & Maven

This is a simple Maven example (a pom.xml file) that uses the to start and stop a MySql container:

Run “mvn docker:start” to start a MySql container with a random mapped port. The command will wait for the container to be ready (it looks for a “ready” string within 20 seconds). After the command succeeds, the container will be running in the background. Run “mvn docker:stop” to stop the started container.

To avoid errors in this shape:

You need a recent version of the For example, 0.38.1. It also works with the current latest version, 0.40.2.

Actually, the first time I tried this project, it did not work (not because of the above error), but after a recent update, it started to work, maybe because of this added link:

Note that not all the images are available for this architecture “aarch64”. For example, if you try to use this older version of “mysql”, you get this error:

However, you can force the intel architecture for an image, as documented here, e.g., with this environment variable correctly set:

For example, for the above Maven project:

If you now enter the container, you can verify that you’re running an x86_64 image:

However, such images will run slower because they are emulated:


I also use Testcontainers to start Docker containers from the JUnit tests in my projects.

For example, I’m using this example from my TDD book, and it works out of the box.

Eclipse Docker Tooling

Currently, the Eclipse plugin for Docker, Docker Tooling, does not work: it cannot connect to Docker. This has been reported (, and a patch is available to make it work: follow the instructions detailed here I tested it, and it works:

To summarize, using Docker on macOS m1 seems to work fine! 🙂

Installing Arch Linux the (not so) hard way

After using EndeavourOS, an Arch-based distro, for some time with much pleasure and appreciating Arch mechanisms (packages and AUR), I decided it was time to try the “real thing” and install Arch the “hard way” 🙂 Spoiler: it’s not that hard!

I thought it was hard. For sure, it’s more complicated than other distro installation procedures, but, to be honest, after using Linux for more than 20 years, I thought there was not much to be scared of 😉

I now use Arch (besides other distros) on my machines greatly. Of course, I did many experiments with virtual machines before installing Arch on bare metal. I know there are many guides and tutorials, but I’d like to summarize my steps for installing Arch (with a SWAP partition, an EXT4 partition for data to be shared among distros, and a BTRFS primary partition). In particular, in this blog post, I’ll describe my steps for installing Arch on a virtual machine, which, as I’ve just said, it’s the best way to get confident with Arch and not be scared of installing Arch on a real computer. Moreover, on many guides, I noticed a few missing points, which, instead, are essential.

Of course, the best reference is the excellent official guide, and I’ll use the official guide as a reference while following along, Note that there are still a few parts in the guide that refer to other parts of the excellent Arch wiki, and I had a few minor problems the first time I tried the Arch installation.

Here we go!

Create and configure the virtual machine with enough disk space (dynamically allocated so you won’t waste space on your disk), let’s say 100Gb. Make sure you enable EFI in the virtual machine configuration. Of course, insert the Arch Linux ISO as a live CD in the virtual machine. I’m going to use archlinux-2022.09.03-x86_64.iso.

As described in a previous post, I’d suggest performing the installation by connecting via SSH to the virtual machine. This way, you’re using a local terminal: copy and paste will work (since you’re on a local terminal). In particular, since the Arch installer is textual, being able to copy and paste commands from a local terminal makes everything easier. Moreover, the keyboard layout will be the host system’s keyboard layout. Thus, the keyboard layout will be already configured correctly. While in the virtual machine, you’d have to configure the keyboard layout.

(On a side note, even when installing Arch on a real computer, I prefer doing that via SSH, of course, from another computer.)

Before starting the virtual machine, we must map the SSH port of the virtual machine to a local port to connect from our computer. This requires knowing the name you gave to your virtual machine. In this example, I called the virtual machine “Arch Gnome” (because I’ll then install Gnome on the Arch installation). We must run these instructions from the host computer:

Port 2522 is the one we’ll have to use later for connecting to the virtual machine via localhost. Of course, feel free to use another free port number as long as you’ll use it consistently from now on.

Start the virtual machine:

Inside the live environment, the SSH server is already up and running. However, since we’ll connect with the root account (the only one present), we must give the root account a password. By default, it’s empty, and SSH will not allow you to log in with a blank password. Choose a password. This password is temporary, and if you’re in a trusted local network, you can choose an easy one.

Now, we can connect via SSH to the virtual machine through localhost. if you have already connected via SSH to localhost, you might get an error of the shape:

All you have to do is edit the known_hosts file by removing the offending lines and try again. You will have to remove all the lines that start with “[]:2522”.

Note that we’re using port 2522 because we previously used that for creating the port mapping. Let’s connect to the virtual machine and type the password we have previously specified for the root account inside the virtual machine (Accept the fingerprint when asked.):

In your local terminal, you see that you get the colors of the virtual machine (now, you’re inside the virtual machine):

Let’s set the console keyboard layout (the default layout is US; if that’s fine with you, skip the next step). This step is not strictly required for our local terminal: even if we’re inside the virtual machine, we’re using our local terminal, so we already use the correct layout. However, let’s do that anyway since we want to simulate an actual installation. Moreover, having the proper layout is good if we want to run commands directly from the VirtualBox window.

I already know the layout I want for my Italian keyboard, so I run:

If you don’t know the exact layout, you can list the available ones with

You can verify in the VirtualBox window that the layout is applied correctly.

Since we are in a virtual machine, the machine should already be able to access the Internet if your host is correctly connected (and that’s required to install Arch Linux). However, if you want to simulate what you would do with an actual installation on bare metal, you can ping a remote host and verify that everything’s OK:

Before going on, as suggested in the official guide, it’s better to make sure the system clock is accurate by enabling network synchronization NTP:

Partitioning the disk

How to partition the disk is your choice. In this example, I will partition the disk according to my needs. However, you need at least two partitions: one for booting in UEFI mode and one for the root filesystem.

In this example, I’ll create four partitions:

  • the one for booting in UEFI mode, formatted as FAT32, 300Mb (it should be enough for UEFI, but if unsure, go on with 512Mb)
  • a swap partition, 20Gb (I have 16Gb, and if I want to enable hibernation, i.e., suspend to disk, that should be enough)
  • a partition meant to host common data that I want to share among several Linux installations on the same machine (maybe I’ll blog about that in the future), formatted as EXT4, 30Gb
  • the root partition, formatted as BTRFS, the rest of the disk

To do that, I’m using cfdisk, a textual partition manager, which I find easy to use.

Now, it is time to get to know the device name of our disk using the command lsblk:

As you might guess, sda is the disk we are installing Arch Linux upon. In a virtual machine, that’s probably always like that. On a real machine, it might be different (for example, if you have an NVME SSD, it will be something like nvme0n1). It’s needless to say that using the correct device name is crucial, especially on a real machine, or you might end up wiping away essential data. The nice thing about a virtual machine is that you’re in a “sandbox,” so, at worst, you’ll break your virtual machine.

So I run

If it is a new virtual machine, you’ll be asked a partition table: choose gpt.

Start creating your partitions. Just use the menus of cfdisk; it’s easy (on the bottom, you will find some help). Once you create a partition, set the “Type” correctly. By default, the type is “Linux filesystem”. For UEFI, you have to specify the type “EFI System,” and for the swap partition, “Linux swap”.

That’s my final result:

Let’s “Write” the partition table to disk and “Quit”. We can also verify with lsblk that the result is as expected:

Of course, you must know which partition is meant for what. In my example, sda1 is for UEFI, sda2 for swap, sda3 for my shared data, and sda4 for root.

Format the partitions

According to my intended layout shown above, I’ll format the four partitions with the following commands:

Mount the partitions

This is also delicate, so you must use the correct device names. What follows is, of course, correct according to my layout.

First of all, let’s deal with the swap partition:

The presence of the BTRFS file system for the root partition makes things a bit more interesting (or a bit more complicated, as you prefer 😉

First, we must mount the BTRFS filesystem on /mnt. Note that we are mounting the BTRFS on /mnt only temporarily and to create the subvolumes (in a minute, we will mount the subvolumes in their final shape on /mnt, together with the other partitions):

This will allow us to create the subvolumes. Again, what follows is the BTRFS subvolume layout I prefer. You might want to choose a different one. To use Timeshift, you must have at least @ for / and @home for /home. This is how I create the subvolumes I want:

As I said, this mount was temporary, just for creating subvolumes. In fact, we now unmount /mnt:

And we mount every single subvolume in its final “position” inside /mnt by also specifying a few additional options like the general “noatime” and the BTRFS-specific “compress” to enable the ztsd compression:

The “-m” option makes mount create the target directory if it does not exist.

Finally, we can mount the remaining partitions. The UEFI one should be mounted to “/boot/efi” inside “/mnt”. I like to mount the “common” partition in “/media/bettini/common” inside “/mnt” because that’s where I’ll use it (relying on the fact that I’ll create a user “bettini” for myself). Again, choose something else for yourself. These are the commands:

This is the final layout of /mnt, which, remember, is where our system will be installed:

Select the mirrors

This part, documented in the official installation guide, is usually skipped in several blog posts I found online. Instead, this step is essential.

The mirrors are specified in the file /etc/pacman.d/mirrorlist.

The guide says:

On the live system, after connecting to the internet, reflector updates the mirror list by choosing 20 most recently synchronized HTTPS mirrors and sorting them by download rate.

The higher a mirror is placed in the list, the more priority it is given when downloading a package. You may want to inspect the file to see if it is satisfactory. If it is not, edit the file accordingly, and move the geographically closest mirrors to the top of the list, although other criteria should be taken into account

You can verify that by inspecting the file /etc/pacman.d/mirrorlist. In my case, the Italian mirror is the last one, so it will be given the lowest priority. This sounds wrong to me. In particular, the documentation also points out:

This file will later be copied to the new system by pacstrap, so it is worth getting right.

Thus, I prefer to run the program reflector myself (see the reflector documentation for the single arguments; of course, I’m using “Italy” as the country because that’s where I leave; I could also specify several values separated by a comma, e.g., “Italy,Germany”):

The following step does not seem to be required in the installation guide. However, to make sure we have an updated PGP keyring (for checking signatures of packages), at this point, I also run:

Running pacstrap

Now, it’s time to install the base packages, Linux kernel, and firmware for standard hardware using the pacstrap script. You specify the target directory, which, as you might guess, it’s /mnt, and the packages.

This is the command I run (I prefer to use the LTS kernel; if you want the latest kernel, use “linux” package instead of “linux-lts”; you can also install them both and then select one from the grub menu):

This command will download about 500Mb, which might take time depending on your Internet speed.

Configuring the system

Since we have already manually mounted all our partitions (on /mnt), the Arch ISO can generate for us the file fstab automatically through the command genfstab:

The “-U” option tells genfstab to use UUID to refer to partitions (alternatively, “-L” can be used to use labels instead).

You can have a look at the result (of course, UUID will be different in your case):

Note that although we used the option “compress=zstd” when mounting our BTRFS subvolumes, genfstab turned that into “compress=zstd:3” because “3” is the default compression value for zstd in BTRFS. If we wanted to make the compression value explicit, e.g., “1”, we should have done that when mounting the subvolumes. Of course, you can always tweak the generate fstab as you see fit.

Now, we can “enter” our installation with “chroot” or, better, with the enhanced arch-chroot, which automatically binds other things like /dev and /proc:

The root directory is what’s inside /mnt, so / refers to what’s inside /mnt. Also, the prompt has changed to reflect this:

We now set the timezone of the installed system. You must use Region/City according to your location. Timezones are available in the directory /usr/share/zoneinfo/. In my case (Italy), I run:

Then, we use hwclock to set the Hardware Clock from the System Clock:

Then, we edit /etc/locale.gen and uncomment the locales we need; in my case, en_US.UTF-8 UTF-8 and it_IT.UTF-8. Since I already know the two locales, instead of editing the file (where all locales are commented out), I append the two locales to the end of that file:

And we generate the locales:

We must also create the /etc/locale.conf file, and set the LANG variable accordingly. This can be done as follows:

We also make permanent the initial changes to the console layout (remember, I used “it”; in your case, you need to use the code you previously specified):

We’ll deal with the network configuration (of the installed system) in a minute. But we can already create the files /etc/hostname and /etc/hosts. You have to choose your preferred hostname. In this example, I’m going to use “arch-vm-gnome”. So I generate the two files with the following two commands:

The boot loader

Since we use BTRFS, we might want to tweak the file /etc/mkinitcpio.conf with these two modules:

And regenerate the initramfs images:

Let’s now install the bootloader. I prefer GRUB. So let’s install a few packages:

During the installation, you might want to take note of the recommendation for installing and configuring grub and the optional dependencies:

Now we install grub in the UEFI partition. Note that, unlike standard GUI Linux installations, you can specify the “–bootloader-id”, which will be the identifier of this grub installation in UEFI. This is useful if you have several bootloaders on your machine. In this example, I’m using ArchGnome:

Hopefully, the installation should succeed, and we can generate the grub menu:

Here’s the output of these two commands:

User accounts

Don’t forget to set the following passwords, or you will not be able to log in to the installed system once you reboot later.

We can now set the root password. This is the effective password for root in the installed system. This should be chosen carefully:

I prefer to use “sudo”, so I first install that

This is also the moment to create your own user account; in my case, it is “bettini”.

I add the user to a few essential groups, in particular, “wheel” which makes my user a superuser account. Just relying on the group “wheel” is not enough: we must allow members of the group wheel to execute any command. This is done by uncommenting this line in the /etc/sudoers: “%wheel ALL=(ALL:ALL) ALL”. A sed command will accomplish that:

That would be enough to reboot and try our installation. However, we would have no networking (actually, since this is a virtual machine, networking should work out of the box since you don’t need to configure any WiFi network, for example) and no desktop environment. So let’s go on with some further installations and configurations:

Install Gnome

In this example, I’m going to install the GNOME desktop environment. Besides GNOME, I’m installing other necessary packages like the “NetworkManager” (for easily configuring networking in GNOME), “firewall”, “firefox”, the package for choosing a power profile (useful for laptops), and other base packages, including the kernel headers (here I’m using “linux-lts-headers” because I installed the LTS kernel; otherwise, use “linux-headers”):

About 700Mb will be downloaded.

Once done, we have to enable the services at boot (in particular, GDM, the login manager, and the NetworkManager):

Time to reboot!

Now, it’s time to leave the environment and unmount all the partitions:

And reboot into our new Arch Linux installation.

If everything goes fine, we should see the login manager, and we can enter Gnome:

So, in the end, it’s not so hard to install Arch 😉

Maybe it’s a long procedure, but… most of that can be scripted! That’s will be the subject of another blog post, so stay tuned! 🙂

Linux EndeavourOS Cassini Review

I have already blogged about EndeavourOS, which I use most of the time on all my computers (desktops and laptops). Since EndeavourOS, based on Arch, is a rolling release, I update it almost daily and don’t need to install it from scratch when a new release comes out, like Cassini, released a few days ago. However, I wanted to try this new release by installing it from scratch (of course, using BTRFS).

I’ll first go through the installation, but I can anticipate that, once again, I’m impressed by EndeavourOS. This installation is smooth, with some novelties: you can choose between “grub” and “systemd-boot” now. Moreover, they switched from “mkinitcpio” to “dracut” for the generation of initramfs (this requires a few adjustments if you want to enable hibernation). Most of all, EndeavourOS is pure Arch but with outstanding defaults. Indeed, the KDE and GNOME environments are vanilla ones, with only a few customizations.

This time, I’ll install GNOME (but I’ll briefly say something about the KDE version at the end of this article).


First, I’m using Ventoy to boot the installer because I keep several Linux ISOs on the USB stick. I first made sure to update Ventoy because I read that the old versions did not work correctly with the latest Arch-based ISOs. Moreover, once I selected the “Cassini” ISO:

I got another menu (that’s something new), where I selected GRUB:

As usual, the first thing to do, once booted into the live environment, XFCE, is set up the network connection. You might also want to change the keyboard layout (Disable system defaults and install your layout, in my case, it’s the Italian layout):

Then, let’s update the mirrors (typically by selecting your state and possibly another one near you) with the “Welcome” application:

Then, let’s start the installer.

I choose the “Online” method because I want to install GNOME instead of Xfce.

I always prefer to install any operating systems in English, so I select “American English”:

The location has been found successfully, and while the language is the one I chose, it proposes to use my Italian locale for dates and numbers:

After setting the keyboard layout, I select the GNOME desktop:

And then, I can select the single packages. Since you choose the packages after selecting the desktop, a few packages, in particular, the ones of the chosen desktop, have already been selected:

Then, I typically unselect “xf86-video-intel”, which is known to give a few problems:

And I select all the packages concerning “Printing Support” (including “HP”) and also the “LTS kernel in addition” (because if anything goes wrong with the latest kernel, I can switch to the LTS one).

Now there’s a big novelty: you can select the bootloader. The default is “systemd-boot”, but I prefer to stay with GRUB (since I know it works for my use cases like booting Timeshift snapshots and booting other distros):

It’s time for partitioning. Since I have another (EndeavourOS) Linux installation and Windows on this computer, I choose Manual partitioning:

First, I specify to mount the existing EFI partition (into “/boot/efi”) without formatting it and ensure the “boot” flag is selected. This way, the installer can properly install GRUB.

Then, I select the (existing) partition I’m going to replace with this installation; I specify to format it as BTRFS and mount it as the root partition:

There’s also an existing EXT4 partition that I use to share common data among my Linux installations, so I select that (without formatting it) and specify my desired mount point:

That’s the final layout of the primary SSD disk:

I also have another HD on this computer with some existing partitions I want to access from the installed system. So I select this other disk from the top drop-down list, and I specify the final mount points:

As usual, there’s the “Users” section, which I will not detail.

Finally, we can have a look at the summary, which looks good to me:

OK, let’s start the installation!

The installation went fine, and it took about 5 minutes. This is an old computer (6 years old), so it was pretty fast.

First impressions

Here we are in the installed system!

As usual, the GRUB menu is beautifully pink and purple:

And here’s GNOME:

You see that these are not the standard GNOME icons. In fact, EndeavourOS provides GNOME with the “Qogir” icons, which look great to me:

Besides that, I seem to understand that’s vanilla GNOME (it does not have “minimize” and “maximize” buttons enabled by default). It’s good that “GNOME Tweaks” is already installed. However, there’s no GNOME extension installed (the first one I installed was “AppIndicator and KStatusNotifierItem Support”). The terminal is the new “GNOME Console”, which I’ll soon replace with the old “GNOME Terminal” (I prefer that one). Standard GNOME applications like “Calendar” and “Contacts” are not installed either. At least, we have the new GNOME Text editor, which replaces the (IMHO) unusable “Gedit”.

There are no standard GNOME wallpapers, just the EndeavourOS one. Of course, you can install other wallpapers using the “Welcome” app. However, I’ll switch to Variety soon.

The Wayland session is enabled by default. It’s OK to me because GNOME and Wayland are usable nowadays.

The BTRFS subvolumes were created as expected, that is, with a separate “@home” and separate subvolumes for “/var/log” and “/var/cache”. Unfortunately, we also have nested subvolumes for “/var/lib/machines” and “portables”, which are known to give headaches if you restore a Timeshift snapshot:

Thus, since I’m not planning to use them, I removed them (this will make the two nested subvolumes disappear), and I recreated them as standard directories (I seem to understand that those two directories must exist; otherwise systemd will recreate them anyway):

So it’s time to start installing my applications and tweak a few GNOME settings.

In particular, since I have both the “linux” and “linux-lts” kernel, the GRUB configuration sets the LTS version as the default. To change that, I edit “/etc/default/grub” and specify

And regenerate the GRUB configuration:

Concerning KDE

I have also tried EndeavourOS KDE on a virtual machine. With Cassini, you get a new custom (and beautiful) Plasma theme, dark and purple (as usual in EndeavourOS).

To summarize

To summarize, EndeavourOS once again proved to be a fantastic Arch-based Linux distribution:

  • Easy to install
  • Arch-based (e.g., not like Manjaro)
  • not bloated with too much software
  • close to the vanilla DEs (at least for the ones I tried, GNOME and KDE)
  • with a wonderful and warm community, by the way 🙂

Happy new Linux year! 🙂

Enabling Hibernation in EndeavourOS (with dracut)

EndeavourOS is an Arch-based Linux distro, so the technique for enabling hibernation for an Arch-based distro should work out of the box. That was true until the new ISO released this December, “Cassini”, where they switched from “mkinitcpio” to “dracut”.

Now, a few things must be adapted to make hibernation work in EndeavourOS, and the automatic tool described in my previous post can no longer be used. However, don’t worry; it’s not hard to enable hibernation. In this post, I’ll describe the steps to enable it.

IMPORTANT: my instructions assume that you have a SWAP partition; for a SWAP file, you’ll have to adapt a few things, but I can’t help since I never use SWAP files.

First, you need to have a swap partition already set up. For example, in /etc/fstab, you should have something like

The UUID is essential, and you should take note of it.

Then, we must tell “dracut” about hibernation so that it generates initramfs accordingly. It is enough to create the file “/etc/dracut.conf.d/resume.conf” with this line:

By the way, a quick way to do that is by running this command:

Then, we regenerate initramfs:

Now, edit your /etc/default/grub and specify resume in GRUB_CMDLINE_LINUX_DEFAULT, with the UUID of your swap partition. So it should be something like (remember that <UUID of your swap partition> must be replaced with the UUID; the “…” refer to existing options):

Save the file and update the grub configuration:

Now, reboot and hibernate should work. You can try that with the following:

You may also want to refer to the older post enabling hibernation in Linux, particularly in Ubuntu, for other mechanisms related to hibernation, like suspend and then hibernate.

Acer Aspire Vero, Unboxing and First Impressions

During the last “Black Friday,” I bought this laptop:

Acer Aspire AV15-51-58J2 39,6 cm (15.6″) Full HD Intel® Core™ i5 16 GB DDR4-SDRAM 512 GB SSD Wi-Fi 6E (802.11ax) Windows 11 Home. (Intel Evo certified, by the way)

I paid 599 instead of 899 euros, that’s a significant discount (especially for its features, mentioned above and detailed in the post)!

This laptop caught my attention a few months ago due to its “eco” and “green” construction choices. You might want to look at to know more about the recycled materials used for its construction. Besides these “green” appealing characteristics, I immediately found the laptop “beautiful” and pleasant at the touch.

In this blog post, I’ll describe my first impressions.

Here’s the unpacking. As you can see, the packaging is made of recycled paper:

The charger is not USB-C, unfortunately. However, it is pretty light. And the only USB-C port CAN NOT be used for charging, but only for data transfer:

And look: one of the containers can be used as a stand for the laptop (excellent!):

Besides the standard papers and a very minimal getting started guide (pretty useless), there are also some ecological stickers 🙂

Also, the inverted “R” and “E” (for the three words “Reduce”, “Reuse”, and “Recycle”) are a slight distinguishing touch (they’re on the keyboard cover as well):


And there are lots of ports (right side: USB 2 and headphone Jack; left: one HDMI, two USB 3, one USB-C – only data, no recharging, and an ethernet port, besides the recharging port). To be honest, I did not find the ethernet very comfortable to use, but maybe I need to get used to it.

This means there’s no SD card reader… Too bad!

The back shows that it should be easy to open the laptop (standard screws), though I still haven’t tried that:

Oh, and there are no painted elements on the laptop: there are engraved symbols and writings:

You see that the surface is anything but smooth, but that makes the laptop very pleasant to touch. Probably the best laptop surface I have ever had! Yes, that part is mostly recycled too, which is ecologically good! 🙂

The two lights on the right will tell you when the battery is charging (“red”: charging, “blue”: fully charged) and when the computer is on:

Unfortunately, these are the only LEDs provided by this laptop. This means that unless the operating system tells you something, you cannot know whether the num lock or the capslock is selected.

When you open the laptop (you can also open it with one hand), the screen slightly lifts the back of the laptop so that the ventilation works better.

Let’s turn it on and enjoy the nice booting logo:

The keyboard is backlit, and the light is good enough to use the keyboard comfortably. Unfortunately, after the backlight turns off because you haven’t used it for a while, just using the touchpad will not turn the backlit back on: you have to press a key.

The keyboard is lovely! At least, that’s the best keyboard I’ve used. Yes, better than Mac air, DELL XPS, and LG GRAM. It’s a great pleasure to type on this keyboard. The only bad part of this keyboard is that the “ENTER” key (“Invio” in Italian) is large enough but too close (actually, attached) to the other key (“ù” in the Italian version). But that’s a problem only for the first minutes of usage. I got used to it.

On the contrary, the touchpad is only “OK”. My other above-mentioned laptops have a much better touchpad. Having said that, this touchpad is usable anyway, though a bit tougher in some external points. If you click on the center, you have no problem. The touchpad is probably too smooth, and if your fingers are wet, you will experience some friction.

Another mechanism I had to change immediately was the function keys. As it happens in several modern laptops, by default, a function key does not behave like a function key but as the other functionality provided by the key (e.g., volume adjustment, light adjustment, etc.). Luckily, I almost immediately found the procedure to achieve that (once and for all) on the Acer website:

  1. Enter the BIOS (you must press F2 while the laptop is booting when you see the “Acer” logo; in the BIOS, you can press Ctrl+S to enable hidden and advanced features; that’s not required for what I need to do in this specific case).
  2. Then, in the main tab, you must change the “Function key behavior” to “Function Key”. Save and restart.

The laptop comes with Windows 11 Home installed. If you read my blog, you know that I’m not a Windows fan 😉 (I have already installed a few Linux distros in multi-boot, and I’ll blog about that in the future). However, Windows 11 is not that bad (well… it’s still Windows 😉 The theme provided by Acer is, of course, “green,” and I like it:

Of course, there were already several updates to apply, which took about an hour! The Windows installation already comes with Firefox installed and several other software. Unfortunately, this includes Norton security, which I had to uninstall immediately (too heavyweight).

The screen resolution is 1920×1080 60,05 Hz and by default Windows already configures 125% scaling. I had to turn it to 150%. Otherwise, it was too fainting for my eyes to using it.

Power consumption is good. I’m used to LG GRAM, Dell XPS, and Mac air, which provide much better battery time. However, this Acer is not impaired in that respect: they say it should provide about 9 hours of battery time. Of course, it’s less, but I found that by using it pretty intensively, you get 5 hours. In that respect, Acer provides its own application (just search for “Vero” in Windows) for choosing a battery profile:

Note that between the usual “Powersave” and “Balanced,” you have an additional “Eco Mode”.

This program also allows you to set the battery charge limit (to increase battery life):

Moreover, there’s also the program “Acer Aspire Center” with other valuable utilities:

To summarize, my first impressions are great! I like this laptop, and for the price I paid, it has excellent features. Moreover, as I said above, I also find it very beautiful. I appreciate the “green” and “eco” ideas and design choices behind this laptop!

Concerning portability, my Dell XPS 13, LG GRAM 16, and Mac air are, of course, better, but in the end, this laptop weighs less than 2 Kg, so it’s heavier than the others mentioned, but still portable enough.

Stay tuned for more blog posts on this laptop, especially concerning Linux on this laptop 🙂

Installing Arch Linux on a PineBook Pro (external storage)

This post will describe my experience installing Arch Linux on a PineBook Pro on external storage (USB stick in this example). Thus, the Manjaro default installation on the eMMC will not be touched. You should use a fast USB Stick, or the overall experience will be extremely slow. I’m using a SanDisk Extreme PRO, which is fast and feels like an SSD. This USB stick will be erased entirely, so don’t use a USB stick with essential data on it.

The post is based on the instructions found at

The installation process consists of two steps:

  • First, install the official Arch Linux ARM distribution; this will not be enough to have many hardware parts working (e.g., WiFi, battery management, and sound).
  • Then, add the repositories with kernels and drivers for the PineBook Pro.

The first part must be performed from an existing Linux installation on the PineBook Pro. I will use the Manjaro installation that comes with the PineBook Pro. The second part will be performed on the installed Arch Linux system on an external drive (a USB stick in this example). Since after the Arch Linux installation, the WiFi is not working, for this part, you need to connect to the wired network, e.g., with an ethernet USB adapter.

Finally, I’ll also show how to install Gnome.

First part

This is based on

I insert my USB stick, which is /dev/sda. (Use “lsblk” to detect that.) From now on, I’m using this device. In your case, the device name might be different.

From now on, all the instructions are executed as “root” from a terminal window; thus, I first run:

I will do the following steps in a directory of the root’s home:

We need to download and extract the latest release of Tow-Boot for the Pinebook Pro from At the time of writing, the latest one is “2021.10-005”:

Now we flash Tow-Boot to /dev/sda (replace this with the device you are using).

Remember: this will wipe all the contents of the USB stick. Moreover, make sure you specify the right device, or you might overwrite the eMMC of the computer.

This process creates the partition table for the device, with the first partition for Tow-Boot. This created first partition must not be modified further. In fact, as you see in a minute, when we further partition the disk, we make sure to skip the first partition.

The output should be something like this:

Now, we must create the partitions on the USB stick. The process is documented step-by-step here, and must be followed strictly:

These are the contents of my terminal where I follow the above-mentioned instructions (since I had already used this USB stick for some experiments before writing this blog post, fdisk detects the existing ext4 signature):