Creating Alpine Linux APKBUILD Files: Master Package Development
I’ll show you how to write proper APKBUILD files for Alpine Linux packages. After maintaining dozens of packages for Alpine, I’ve learned the ins and outs of creating clean, maintainable build scripts that work reliably.
Introduction
APKBUILD files are the heart of Alpine’s package system. They’re shell scripts that tell the package manager how to download, compile, and install software. Think of them as recipes - they need to be precise, complete, and reliable.
I’ve been writing APKBUILD files for years, and the format is actually pretty elegant once you understand the structure. Alpine’s approach keeps things simple while being incredibly powerful.
Why You Need This
- Create custom packages for your organization
- Contribute packages to Alpine Linux repositories
- Maintain internal software distributions
- Understand Alpine’s packaging philosophy
Prerequisites
You’ll need these things first:
- Alpine Linux development environment
- Basic understanding of shell scripting
- Familiarity with build systems (make, cmake, etc.)
- Knowledge of the software you’re packaging
Step 1: Understanding APKBUILD Structure
Basic APKBUILD Anatomy
Let’s start with the fundamental structure every APKBUILD needs.
What we’re doing: Creating a minimal but complete APKBUILD template.
# Create a new package directory
mkdir -p ~/aports/testing/example-app
cd ~/aports/testing/example-app
# Create basic APKBUILD
cat > APKBUILD << 'EOF'
# Contributor: Your Name <[email protected]>
# Maintainer: Your Name <[email protected]>
pkgname=example-app
pkgver=1.0.0
pkgrel=0
pkgdesc="Example application for demonstration"
url="https://github.com/example/example-app"
arch="all"
license="MIT"
depends=""
makedepends="gcc libc-dev make"
install=""
subpackages="$pkgname-dev $pkgname-doc"
source="$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz"
builddir="$srcdir/$pkgname-$pkgver"
build() {
cd "$builddir"
make
}
check() {
cd "$builddir"
make test
}
package() {
cd "$builddir"
make DESTDIR="$pkgdir" install
}
sha512sums="
abcd1234... example-app-1.0.0.tar.gz
"
EOF
Code explanation:
pkgname
: Package name (must match directory name)pkgver
: Upstream version numberpkgrel
: Alpine-specific package release numberarch
: Supported architectures (“all”, “x86_64”, “aarch64”, etc.)depends
: Runtime dependenciesmakedepends
: Build-time dependencies only
Variable Definitions
What we’re doing: Understanding key APKBUILD variables and their purposes.
# Package metadata
pkgname=myapp # Package name
pkgver=2.1.0 # Version from upstream
pkgrel=1 # Increment for Alpine changes
pkgdesc="Brief description" # One-line package description
url="https://example.com" # Project homepage
# Architecture support
arch="all" # Builds on all architectures
arch="x86_64 aarch64" # Specific architectures only
arch="noarch" # Architecture-independent
# Dependencies
depends="python3 py3-requests" # Runtime dependencies
makedepends="python3-dev py3-setuptools" # Build dependencies
checkdepends="py3-pytest" # Test dependencies
Variable explanation:
pkgrel
: Reset to 0 for new upstream versions, increment for Alpine-specific changesarch="all"
: Package builds and works on all Alpine architectures- Dependencies use Alpine package names, not upstream names
Tip: I always check what other similar packages use for dependencies. The Alpine package database is your friend here.
Step 2: Writing Build Functions
The build() Function
What we’re doing: Writing a proper build function that compiles source code.
build() {
cd "$builddir"
# Configure build (autotools example)
./configure \
--prefix=/usr \
--sysconfdir=/etc \
--mandir=/usr/share/man \
--localstatedir=/var \
--disable-static \
--enable-shared
# Build the software
make
}
For CMake projects:
build() {
cd "$builddir"
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_SHARED_LIBS=True \
-DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build
}
For Python projects:
build() {
cd "$builddir"
python3 setup.py build
}
Build function explanation:
- Always use
cd "$builddir"
first - Use standard Alpine paths (
/usr
,/etc
,/var
) - Disable static libraries unless specifically needed
- Use
RelWithDebInfo
for debugging symbols
The check() Function
What we’re doing: Adding test execution to ensure package quality.
check() {
cd "$builddir"
# Run test suite
make check
# For Python packages
python3 -m pytest
# For CMake projects
cmake --build build --target test
# Custom test commands
./build/bin/myapp --version
./build/bin/myapp --self-test
}
Code explanation:
check()
function is optional but highly recommended- Tests should run quickly and not require network access
- Test failures should cause package build to fail
- Include basic functionality tests when possible
The package() Function
What we’re doing: Installing files into the package directory structure.
package() {
cd "$builddir"
# Standard make install
make DESTDIR="$pkgdir" install
# For CMake projects
DESTDIR="$pkgdir" cmake --install build
# For Python projects
python3 setup.py install --prefix=/usr --root="$pkgdir"
# Manual file installation
install -Dm755 myapp "$pkgdir/usr/bin/myapp"
install -Dm644 myapp.conf "$pkgdir/etc/myapp/myapp.conf"
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
}
Installation explanation:
DESTDIR="$pkgdir"
: Redirects installation to package directoryinstall -Dm755
: Sets file permissions (755 for executables)install -Dm644
: Sets file permissions (644 for regular files)- Always use absolute paths starting with
$pkgdir
Step 3: Advanced APKBUILD Features
Subpackages
What we’re doing: Creating multiple packages from one APKBUILD.
# Define subpackages
subpackages="$pkgname-dev $pkgname-doc $pkgname-lang $pkgname-openrc"
# Development package function
dev() {
pkgdesc="$pkgdesc (development files)"
depends="$pkgname=$pkgver-r$pkgrel"
amove usr/include
amove usr/lib/pkgconfig
amove usr/lib/*.so
}
# Documentation package function
doc() {
pkgdesc="$pkgdesc (documentation)"
install_if="$pkgname=$pkgver-r$pkgrel docs"
amove usr/share/doc
amove usr/share/man
}
# Language package function
lang() {
pkgdesc="$pkgdesc (translations)"
install_if="$pkgname=$pkgver-r$pkgrel lang"
amove usr/share/locale
}
# OpenRC service scripts
openrc() {
pkgdesc="$pkgdesc (OpenRC init scripts)"
install_if="$pkgname=$pkgver-r$pkgrel openrc"
amove etc/init.d
}
Subpackage explanation:
amove
: Moves files from main package to subpackageinstall_if
: Automatically installs subpackage when conditions are metdepends
: Ensures subpackage depends on main package
Source Handling
What we’re doing: Managing source files, patches, and checksums.
# Multiple source files
source="
$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz
fix-compilation.patch
myapp.initd
myapp.confd
"
# Different source types
source="
https://example.com/releases/$pkgname-$pkgver.tar.gz
$pkgname.git::git+https://github.com/user/repo.git?tag=v$pkgver
fix-musl.patch
"
# Checksum verification
sha512sums="
abc123... example-app-1.0.0.tar.gz
def456... fix-compilation.patch
789ghi... myapp.initd
jkl012... myapp.confd
"
Source handling tips:
- Use
::
to rename downloaded files - Git sources use special syntax with branch/tag specification
- Always run
abuild checksum
to generate checksums - Keep patches small and well-documented
Conditional Logic
What we’re doing: Adding architecture and option-specific behavior.
# Architecture-specific dependencies
case "$CARCH" in
x86_64) makedepends="$makedepends nasm" ;;
aarch64) makedepends="$makedepends libffi-dev" ;;
esac
# Optional features
_with_systemd=no
[ "$_with_systemd" = yes ] && makedepends="$makedepends systemd-dev"
# Version-specific patches
case "$pkgver" in
1.*) source="$source old-version.patch" ;;
2.*) source="$source new-version.patch" ;;
esac
build() {
cd "$builddir"
local _configure_args="
--prefix=/usr
--sysconfdir=/etc
"
# Conditional build options
if [ "$_with_systemd" = yes ]; then
_configure_args="$_configure_args --enable-systemd"
fi
./configure $_configure_args
make
}
Practical Examples
Example 1: C Application with Autotools
What we’re doing: Creating an APKBUILD for a traditional C application.
# Contributor: Your Name <[email protected]>
# Maintainer: Your Name <[email protected]>
pkgname=htop
pkgver=3.2.1
pkgrel=0
pkgdesc="Interactive process viewer"
url="https://htop.dev/"
arch="all"
license="GPL-2.0-or-later"
makedepends="autoconf automake libtool ncurses-dev"
subpackages="$pkgname-doc"
source="https://github.com/htop-dev/htop/archive/$pkgver.tar.gz"
builddir="$srcdir/$pkgname-$pkgver"
prepare() {
default_prepare
cd "$builddir"
autoreconf -fiv
}
build() {
cd "$builddir"
./configure \
--prefix=/usr \
--sysconfdir=/etc \
--mandir=/usr/share/man \
--enable-unicode
make
}
check() {
cd "$builddir"
make check
}
package() {
cd "$builddir"
make DESTDIR="$pkgdir" install
}
sha512sums="
abc123def456... 3.2.1.tar.gz
"
Example 2: Python Package
What we’re doing: Packaging a Python application with proper Alpine conventions.
# Contributor: Your Name <[email protected]>
# Maintainer: Your Name <[email protected]>
pkgname=py3-requests
_pkgname=requests
pkgver=2.28.1
pkgrel=0
pkgdesc="Python HTTP library"
url="https://requests.readthedocs.io/"
arch="noarch"
license="Apache-2.0"
depends="python3 py3-certifi py3-charset-normalizer py3-idna py3-urllib3"
makedepends="py3-setuptools"
checkdepends="py3-pytest py3-pytest-httpbin"
source="https://files.pythonhosted.org/packages/source/${_pkgname:0:1}/$_pkgname/$_pkgname-$pkgver.tar.gz"
builddir="$srcdir/$_pkgname-$pkgver"
build() {
cd "$builddir"
python3 setup.py build
}
check() {
cd "$builddir"
# Skip tests requiring network
python3 -m pytest -k "not test_https_warnings"
}
package() {
cd "$builddir"
python3 setup.py install --prefix=/usr --root="$pkgdir"
}
sha512sums="
def789ghi012... requests-2.28.1.tar.gz
"
Troubleshooting
Common Build Failures
Problem: Package fails to build with missing dependencies Solution: Check and add required makedepends
# Check what files are missing
abuild -r 2>&1 | grep -i "not found"
# Find which package provides missing files
apk search cmd:gcc
apk search so:libssl.so
# Add to makedepends
makedepends="gcc libc-dev openssl-dev"
Checksum Mismatches
Problem: Source file checksums don’t match Solution: Regenerate checksums
# Update checksums automatically
abuild checksum
# Manually verify source files
sha512sum source-file.tar.gz
# Check if source file changed upstream
wget -O - URL | sha512sum
Installation Path Issues
Problem: Files installed in wrong locations Solution: Fix configure options and install commands
# Check where files are being installed
make -n DESTDIR=/tmp/test install
# Use proper Alpine paths
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
# Fix manual installations
install -Dm755 binary "$pkgdir/usr/bin/binary"
Best Practices
-
Package Naming:
# Use consistent naming pkgname=myapp # C/C++ applications pkgname=py3-mylib # Python 3 libraries pkgname=go-myapp # Go applications pkgname=perl-my-module # Perl modules
-
Version Management:
- Follow upstream versioning exactly
- Use
pkgrel
for Alpine-specific changes - Document version changes in commit messages
-
Security Practices:
- Always verify source checksums
- Keep dependencies minimal
- Use secure download URLs (HTTPS)
- Review patches carefully
Verification
To verify your APKBUILD is working correctly:
# Check APKBUILD syntax
abuild sanitycheck
# Test build process
abuild -r
# Check package contents
tar -tzf ~/packages/testing/x86_64/myapp-1.0.0-r0.apk
# Install and test package
sudo apk add ~/packages/testing/x86_64/myapp-1.0.0-r0.apk
Wrapping Up
You just learned how to create proper APKBUILD files:
- Understood the basic structure and required variables
- Mastered build, check, and package functions
- Learned advanced features like subpackages and conditionals
- Practiced with real-world examples
- Troubleshot common build issues
Writing good APKBUILD files takes practice, but following these patterns will give you a solid foundation. The Alpine packaging system is really well designed - once you get the hang of it, creating packages becomes straightforward and reliable.