How to Create a Development Environment on Alpine Linux
Step-by-step guidance for setting up an Alpine Linux workstation for C++ and Java development, with some of my favorite tips and resources.
Join the DZone community and get the full member experience.Join For Free
Alpine Linux has gained great popularity in recent years and is probably the most favored Linux for Docker. Originally designed for routers, it is a secure, fast, feather-light Linux: a basic Alpine base image takes as little as 5 MB, orders of magnitude less than other popular Linux distros. That fact makes it an ideal choice as the base system of Docker images, where small size is desirable, and specifically for OpenJDK Docker images that otherwise take up several hundred MBs.
In this post, I’ll cover my experience in setting up an Alpine Linux workstation for C++ and Java development, with some hopefully useful Alpine know-hows, tips and resources.
So What Do You Get With Alpine Linux?
First, let’s take a glimpse of the main features of Alpine Linux.
The project homepage summarizes it nicely: “Alpine Linux is an independent, non-commercial, general purpose Linux distribution designed for power users who appreciate security, simplicity and resource efficiency.”
The most notable security feature is a hardened, specially patched Linux kernel.
Additional security features include:
- PIE executables. All Alpine executables are built as PIE – Position Independent Executables —which do not depend on absolute memory addresses for their correct operation, much like shared libraries. Such executables are subject to Address Space Layout Randomization (ASLR), a technique in which the kernel dynamically shifts programs between random memory locations, which is useful for preventing certain kinds of attacks.
- Stack smashing protection. This feature is implemented in all Alpine binaries, and allows stack overflowed programs to exit gracefully instead of crashing horribly. It is typically implemented by building with a special C/C++ compiler option (
-fstack-protector-fstrong), that adds stack canaries to the program code. These are compiler generated variables that mark the end of the allocated stack frame in each function, and when overridden, indicate that the stack has overflowed and trouble is ahead, much like “canaries in a coal mine”.
- “Less is more”. Though not a feature per se, Alpine’s minimalistic nature also makes it safer and less prone to attacks, since it carries little to no excess baggage and therefore the possible attack surface is much smaller, especially compared to other Linuxes. Unfortunately, as with any other system, it is not 100% bulletproof.
So, Alpine is as secure as it can be, but its small footprint is what really sets it apart from the other Linuxes. It is built around two components that make it especially compact:
- Busybox. Busybox is an all-in-one, multi-purpose binary. Named by its authors “The Swiss Army Knife of Embedded Linux”, it provides the core functionalities for dozens of standard programs, such as awk, cp, grep, gzip, sh and top. All programs are symlinked to /bin/busybox, which identifies the program to run according to the name it was executed with. It offers high compatibility to the GNU counterparts, typically with a reduced feature set, but worry not – you can usually get the full GNU functionalities by installing the package of the same name, such as grep or tar, or bundle packages like coreutils.
Busybox is significant to Alpine’s small size – using a single static binary reduces the overhead of multiple executables and allows the resulting binary to be effectively size optimized. On Alpine 3.8, it is squeezed into just 780 KB of goodness.
- musl libc. The musl libc library is a compact and compelling alternative to GNU’s libc library, glibc, which is the defacto libc implementation used in most Linux distributions. Compared to glibc, musl is tiny in size: it weighs only 572 KB on Alpine 3.8, compared to glibc’s 3 MB on Ubuntu 16.04, for example (3 MB is the combined size of libc.so and libm.so; in Alpine, libm.so is symlinked to musl.so). But, there’s much more to musl than its small size: it offers stricter POSIX conformance, improved safety, and competitive performance. This comparison chart from ETA labs, authors of musl, gives a very detailed picture of the differences between the libc implementations and highlights musl’s strengths.
But, there’s a catch: having musl as the default libc implementation introduces a major compatibility issue with other Linuxes that are based on glibc. Almost any Linux program is dynamically linked with libc, and while glibc Linux binaries will link against libc.so, Alpine binaries will link against musl.so. Consequently, Linux executables that were built on glibc distros such as Ubuntu and RedHat, will not be able to run on Alpine, at least not out of the box.
What Else Is in Alpine?
- A neat and simple package manager called apk. If you’re already familiar with the apt package manager, there’s no getting used to apk: the basic commands are add, del, search, and update, and you’re good to go. If you’ve written Alpine Dockerfiles, you should already know apk pretty well.
- The default shell in Alpine is the busybox provided ash. As with anything in Alpine, it is also quite minimalistic, and lacks some shell convenience features, such as auto completion. If you’re into bash, like myself, simply install the bash apk package, and use it as your default shell.
Let’s Install Alpine
As I saw it, there were two options: one, using an Alpine Docker for my development work, and two, bringing up a proper Alpine workstation on my laptop. This was my first real encounter with Docker, so I wasn’t sure whether I should dive in that direction, since I wanted to get up and running quickly. Also, at that point, I thought that Docker is essentially a disposable environment that may not be ideal for day to day development (only later I realized that Docker could come handy for that purpose, too).
The Alpine workstation option seemed much more compelling, but having no experience with Alpine either, I wasn’t sure how comfortable it would be for development; with the Docker option, at least I still had my Ubuntu desktop to work with.
Finally, I chose option two, and rushed into installing Alpine on my already dual-booted Ubuntu-Windows Dell XPS 15.
I headed over to the Alpine downloads page. It offers the latest Alpine version (3.8.1, as of writing these lines), available in 8 different ISO flavors. It turns out Alpine runs on pretty much anything, from servers, dockers, and VMs, to embedded ARM devices and Raspberry Pi. Out of the x86_64 images, I contemplated between “Standard” and “Extended”. Since I was aiming for a full fledged desktop environment, I figured Extended would make a better starting point (which proved correct later on) and hit the download button. Alpine ISO – check.
The first Google hit for “install alpine” is the installation page at the Alpine Linux Wiki. The Wiki is an incredible source of information, with just about everything there is to know about Alpine. Really awesome job by the Alpine team.
As described in the installation page, Alpine could be installed in three different modes:
Diskless mode – In that mode, Alpine boots from the installation media, such as a CD or a USB stick, and loaded into RAM. No data is written to disk, and session is discarded upon reboot.
Data mode – Similar to Diskless, but with the /var folder mounted to a writable media, which is persistent between reboots.
Sys mode – A traditional hard disk install.
Clearly, option 3 was the most suitable one for me. Therefore, I proceeded with the below steps on Ubuntu for installing Alpine:
- Created a bootable Alpine installation USB (guide).
- Used gparted for shrinking my existing volumes, making room for the new Alpine partition, and formatted it as ext4.
- Copied the Alpine system to the new partition (guide).
- Regenerated my GRUB menu entries, by running update-grub2.
- Tweaked GRUB entry to allow booting into Alpine (see below).
That was about it. Just a few hours’ work, and I could now log in into my brand new Alpine Linux installation, triple booted with Ubuntu and Windows
Getting Started With Alpine
Now that I was able to successfully boot into Alpine, it was time for some basic setup steps.
Connecting to Wi-Fi
First and foremost, I needed to get a working internet connection.
My laptop doesn’t have a wired network socket, and my USB RJ45 adapter was broken, so my only option was Wi-Fi. This Alpine Wiki page, “Connecting to a Wireless Access Point”, describes the Wi-Fi setup procedure very clearly, so I was very quickly able to connect to my work Wi-Fi network (specifically, I followed the “Manual Configuration” steps).
For getting started with Wi-Fi on Alpine, there are two essential packages that need to be installed: wireless-tools and wpa_supplicant. It turns out, my bet on the Extended ISO paid off: it includes these packages in its offline apk database, in contrast to the standard ISO. This saved me the trouble of using a colleague’s computer for downloading these packages from the Alpine packages catalogue (which is, BTW, pretty awesome!), copying them to a USB stick, mounting it to Alpine and installing the .apk files manually.
Now that I had network connectivity, I was able to update the apk repository indices and install some convenience packages, such as bash, coreutils and nano.
Preparing The Build Environment
At this point, I had a decent working Alpine command line, so I was good for prepping the build environment.
Similar to Ubuntu build-essential, the build-base meta-package is a good starting point for installing the most common build tools and utilities, including g++, make and binutils. For building our C++ stack, I needed several other packages, including cmake and linux-headers.
Some C++ projects are only compatible with certain compiler versions, and specifically, Alpine 3.8 ships with gcc 6.4.0. The official Alpine repositories do not contain previous major gcc versions, as far as I could find, so if you’re needing a different gcc version on Alpine, chances are you’ll find it (or a compatible version) at the musl-cross-make project page: https://github.com/just-containers/musl-cross-make/releases.
I’ve personally tested gcc 4.9.4, and it worked perfectly. Great job by these guys, which could be a real lifesaver for some.
Next, installing Java. The official Alpine JDK implementation is OpenJDK, brought to us by the OpenJDK IcedTea project. OpenJDK versions 7 and 8 are available, and could be downloaded from the Alpine apk repositories. Unfortunately, there are currently no GA builds for Java 9, 10 and 11 (see project Portola homepage and this github discussion for current status). For now, I was satisfied with the openjdk8 package.
Finally, expecting times of fierce debugging, I went ahead and installed gdb and strace, as well as musl and OpenJDK debug symbols, available in the musl-dbg and openjdk8-dbg packages respectively. Bring it on, Alpine!
Tip: for bleeding edge and latest packages, you may want to add the edge repository to your /etc/apk/repositories file, as described in the package management page. For example, lldb is currently only available at the edge. However, edge packages are experimental, so use them with care.
Installing a Desktop and IDE
Alpine has a variety of desktops available, including GNOME, MATE and Xfce. I wasn’t yet familiar with Xfce, so I decided to give it a try. As always, the Alpine Wiki is a great place to start. The “Xfce setup” page details all the steps needed for installing and starting Xfce. Most importantly, the packages to install are xfce4 – the desktop itself, and alpine-desktop. The latter is a very comprehensive meta-package that delivers the default Alpine desktop experience, including the Firefox web browser, AbiWord editor, Audacious sound player, and many others.
Tip: Don’t forget to install a font package as well, such as font-noto. Otherwise, you’ll end up with weird blank squares for text, and some GUI apps may crash or misbehave. This took me a decent amount of time to figure out!
The final piece missing in my Alpine puzzle was finding proper IDEs for both C/C++ and Java development. Here, the musl-glibc compatibility issue becomes really painful. For example, Eclipse CDT builds compatible with musl are not to be found, so without the glibc compatibility layer, it’s not possible to run Eclipse on Alpine. I’m comfortable with the command line, but I really wanted a proper IDE at hand, especially for debugging.
Luckily, we have the JetBrains IDE family. IntelliJ, CLion and other IDEs from JetBrains all run on the JVM, and do not depend directly on the underlying libc implementation. Since the OpenJDK JVM is fully compatible with musl, any standard Java app should run fine out of the box, and so do IntelliJ and CLion. I’m a big IntelliJ fan, so I was thrilled to see that it’s working just as well on Alpine. What a relief!
It’s important to note that while IntelliJ is freely available to everyone in its community edition, CLion is only free for a 30-day evaluation period (though, students can apply for a free academic license). For now, this was enough.
That was it – I was set up with my Alpine desktop environment!
Opinions expressed by DZone contributors are their own.
SRE vs. DevOps
Getting Started With the YugabyteDB Managed REST API
10 Traits That Separate the Best Devs From the Crowd
Integrate Cucumber in Playwright With Java