[Request] After migration from opkg to apk, missing feature: install packages to custom directory

I've been using OpenWrt for years and really appreciate the project's progress.

However, after the recent migration from opkg to apk (starting from 25.12), I've run into a significant limitation that I hope the community can help address.

With opkg, we could use -d <dest> to specify an installation destination, like installing large packages directly to a USB drive or SD card instead of filling up the limited internal flash storage. This was extremely useful for routers with small built-in storage.

But now with apk, this feature seems completely gone. The only "workaround" is setting up Extroot, which requires reformatting external storage and making it the root overlay — not always desirable or convenient when you just want to install a few packages elsewhere without altering the entire system layout.

USB drives are prone to failure. With the opkg method, if a USB drive fails, it only affects the packages installed on it, while the router's core functionality remains intact. However, with apk's extroot approach, a failed USB drive can cause complete system failure.

I understand that apk-tools is designed differently from opkg, but I'd kindly ask the developers to consider adding support for specifying an alternative installation path in future versions. Even if it's a simplified version compared to what opkg offered, something like apk add --dest /mnt/sda1/packages <pkgname> would be a huge quality-of-life improvement for many users.

Thanks for reading, and thanks to all contributors for your hard work on OpenWrt!

Have you tried this?

apk -p </path/to/root>

# example:
apk -p /mnt add nano

From the documentation:

I asked myself the same question some time ago...

There was no "-p" option back then. Is there any way to check if it solves your problem?

update: I did a test on a virtual machine with openwrt the "-p" option works. :+1:

I tried in openwrt-25.10, but failed.

root@Openwrt:~# mkdir /root/pkgs
root@Openwrt:~# apk  -V
apk-tools 3.0.5
root@Openwrt:~# apk update
  [.....omit.....]
OK: 11415 distinct packages available
root@Openwrt:~# apk -p /root/pkgs update
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory
root@Openwrt:~# apk --root /root/pkgs add  nano
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory
root@Openwrt:~# apk -p /root/pkgs add  nano
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory
root@Openwrt:~# apk fetch nano
Downloading nano-9.0-r1
root@Openwrt:~# ls nano-9.0-r1.apk
nano-9.0-r1.apk
root@Openwrt:~# apk -p /root/pkgs add  nano-9.0-r1.apk
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory
root@Openwrt:~# mkdir /mnt/pkgs
root@Openwrt:~# apk -p /mnt/pkgs add  nano
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory
root@Openwrt:~# mkdir /pkgs
root@Openwrt:~# apk -p /pkgs add  nano
ERROR: Unable to lock database: No such file or directory
ERROR: Failed to open apk database: No such file or directory

That error looks like apk is accepting the alternate root, but the target root has no apk database yet. I would try the same test with --initdb, e.g. apk -p /mnt/pkgs --initdb add nano (or first create the minimal /lib/apk/db under that root, depending on the OpenWrt build). Still, this is not a perfect replacement for opkg -d: files will be laid out relative to that new root, so init scripts, UCI defaults and PATH/library lookup may still need explicit bind mounts or symlinks. It is useful to separate “can apk install into another root?” from “will the package actually run cleanly from there?”

Here's the process for testing "apk -p" - before proceeding, it's best to test on a virtual machine
with the official OpenWrt x86_64 image and a second virtual disk as simulated USB drive
so you don't mess up a production router.

Assume your external storage is mounted at /mnt (e.g. mount /dev/sdb1 /mnt).

# 1. Create the minimum directory structure apk expects
mkdir -p /mnt/etc/apk
mkdir -p /mnt/lib/apk
mkdir -p /mnt/var/cache/apk
mkdir -p /mnt/tmp

# 2. Copy repository list and keys from the running system
cp -r /etc/apk/. /mnt/etc/apk/

# 3. Initialize the apk database in the new root
apk -p /mnt add --initdb

# 4. Update package index
apk -p /mnt update

# 5. Install your package (python3 used as example — it has external lib dependencies)
apk -p /mnt add python3

# or nano, ecc ...
apk -p /mnt add nano

Make binaries and libraries available system-wide

After installation, binaries are under /mnt/usr/bin and libraries under /mnt/usr/lib — neither is
in the default search path. Create a persistent profile script:

cat > /etc/profile.d/extpkg.sh << 'EOF'
export PATH="/mnt/usr/bin:/mnt/usr/sbin:$PATH"
export LD_LIBRARY_PATH="/mnt/usr/lib:/mnt/lib"
EOF

# Apply immediately without rebooting
source /etc/profile.d/extpkg.sh

The library dependency problem

Packages with their own shared libraries (like python3, which ships libpython3.13.so.1.0) will fail
at runtime because the dynamic linker only looks in the system root /usr/lib, not in /mnt/usr/lib:

Error loading shared library libpython3.13.so.1.0: No such file or directory

In my tests on OpenWrt 25.10, exporting LD_LIBRARY_PATH in the shell was not sufficient, while passing it inline worked reliably.

LD_LIBRARY_PATH="/mnt/usr/lib:/mnt/lib" /mnt/usr/bin/python3 --version
# Python 3.13.x 

So the cleanest solution is a wrapper script in the system root for each installed binary example:

cat > /usr/bin/python3 << 'EOF'
#!/bin/sh
exec env LD_LIBRARY_PATH="/mnt/usr/lib:/mnt/lib" /mnt/usr/bin/python3 "$@"
EOF
chmod +x /usr/bin/python3


# Test
python3 --version

warning: It's better to create them by hand to avoid problems in my opinion...
thus avoiding having to replace or modify a good binary .

Important warnings before doing this on a real router

  • Test in a VM first. Use the official OpenWrt x86_64 image under QEMU, VirtualBox or Proxmox
    with a second virtual disk as the simulated USB drive. This whole procedure is easy to validate
    safely before touching production hardware.

  • --initdb is mandatory. Without it, apk refuses to open the database on the alternate root with
    "Unable to lock database: No such file or directory", regardless of the path used.

  • Wrapper scripts are in the system root. If you create /usr/bin/python3 as a wrapper and the
    external drive is not mounted at boot, the wrapper will silently fail. Make sure /mnt is mounted
    before any service or script that calls those binaries (e.g. via /etc/rc.local).

  • /etc/profile.d/extpkg.sh depends on the drive being mounted. If /mnt is not mounted when a new
    shell session starts, PATH and LD_LIBRARY_PATH will point to non-existent directories.


ps: I remind you that this is also mentioned for "opkg -d":
"This solution will NOT work out-of-the-box in most cases, OpenWrt packages are designed to be installed in root filesystem and may need additional symlinks or modifications to work at all under the changed path! "

Try again. 3 attempts.

The first Try. failed. (openwrt-25.10)

root@Openwrt:~# mkdir /root/pkgs/
root@Openwrt:~# apk -p /root/pkgs/ --initdb add nano
ERROR: unable to select packages:
  nano (no such package):
    required by: world[nano]
root@Openwrt:~# apk -p /root/pkgs/ --initramfs add nano
OK: 0 B in 0 packages
root@Openwrt:~# apk -p /root/pkgs/ update
OK: 0 distinct packages available
root@Openwrt:~# apk -p /root/pkgs/ add nano
ERROR: unable to select packages:
  nano (no such package):
    required by: world[nano]
root@Openwrt:~# apk fetch nano
Downloading nano-9.0-r1
root@Openwrt:~# apk -p /root/pkgs/ add nano-9.0-r1.apk
ERROR: nano-9.0-r1.apk: UNTRUSTED signature
root@Openwrt:~# apk -p /root/pkgs/ --allow-untrusted add nano-9.0-r1.apk
ERROR: unable to select packages:
  libc (no such package):
    required by: nano-9.0-r1[libc]
  libncurses6 (no such package):
    required by: nano-9.0-r1[libncurses6]
root@Openwrt:~# ls pkgs/
etc  lib  var
root@Openwrt:~# ls pkgs/usr
ls: pkgs/usr: No such file or directory

The second try. There are many errors, but "nano" can be used. (openwrt-25.10)

root@Openwrt:~# mkdir /root/pkgs/
root@Openwrt:~# cd /root/pkgs/
root@Openwrt:~/pkgs# apk -p /root/pkgs/ --initdb add
OK: 0 B in 0 packages
root@Openwrt:~/pkgs# cp -r /etc/apk/repositories.d etc/apk/
root@Openwrt:~/pkgs# cp -r /etc/apk/keys etc/apk/
root@Openwrt:~/pkgs# apk -p /root/pkgs/ update
 [.....omit.....]
OK: 11401 distinct packages available
root@Openwrt:~/pkgs# apk -p /root/pkgs/ add nano
(1/5) Installing libgcc1 (14.3.0-r5)
  Executing libgcc1-14.3.0-r5.post-install
  * execve: No such file or directory
ERROR: lib/apk/exec/libgcc1-14.3.0-r5.post-install: exited with error 127
(2/5) Installing libc (1.2.5-r5)
  Executing libc-1.2.5-r5.post-install
  * execve: No such file or directory
ERROR: lib/apk/exec/libc-1.2.5-r5.post-install: exited with error 127
(3/5) Installing terminfo (6.4-r3)
  Executing terminfo-6.4-r3.post-install
  * execve: No such file or directory
ERROR: lib/apk/exec/terminfo-6.4-r3.post-install: exited with error 127
(4/5) Installing libncurses6 (6.4-r3)
  Executing libncurses6-6.4-r3.post-install
  * execve: No such file or directory
ERROR: lib/apk/exec/libncurses6-6.4-r3.post-install: exited with error 127
(5/5) Installing nano (9.0-r1)
  Executing nano-9.0-r1.post-install
  * execve: No such file or directory
ERROR: lib/apk/exec/nano-9.0-r1.post-install: exited with error 127
5 errors; 1196 KiB in 5 packages
root@Openwrt:~/pkgs# ls lib/
apk                  libc.so
ld-musl-x86_64.so.1  libgcc_s.so.1
root@Openwrt:~/pkgs# ls usr/lib/
libform.so.6        libmenuw.so.6       libpanel.so.6
libform.so.6.4      libmenuw.so.6.4     libpanel.so.6.4
libformw.so.6       libncurses.so.6     libpanelw.so.6
libformw.so.6.4     libncurses.so.6.4   libpanelw.so.6.4
libmenu.so.6        libncursesw.so.6
libmenu.so.6.4      libncursesw.so.6.4
root@Openwrt:~/pkgs# ls usr/bin/
ldd   nano
root@Openwrt:~/pkgs# ls usr/
bin/    lib/    share/
root@Openwrt:~/pkgs# ls usr/share/
terminfo
root@Openwrt:~/pkgs# ./usr/bin/nano --version
 GNU nano, version 9.0
 Compiled options: --enable-tiny --enable-linenumbers --disable-nls --disable-utf8

root@Openwrt:~/pkgs# apk -p /root/pkgs/ --initramfs add
5 errors; 1196 KiB in 5 packages
root@Openwrt:~/pkgs# apk -p /root/pkgs/ add nano
5 errors; 1196 KiB in 5 packages

The third try, "nano" can be used. (openwrt-25.10)

root@Openwrt:~# apk fetch nano
Downloading nano-9.0-r1
root@Openwrt:~# mkdir pkgs
root@Openwrt:~# cd pkgs/
root@Openwrt:~/pkgs# apk extract ../nano-9.0-r1.apk
Extracting ../nano-9.0-r1.apk...
ERROR: ../nano-9.0-r1.apk: UNTRUSTED signature
root@Openwrt:~/pkgs# l
root@Openwrt:~/pkgs# apk extract --allow-untrust ../nano-9.0-r1.apk
Extracting ../nano-9.0-r1.apk...
root@Openwrt:~/pkgs# ls -l
drwxr-xr-x    3 root     root          4096 Jun 18 12:46 lib
drwxr-xr-x    3 root     root          4096 Jun 18 12:46 usr
root@Openwrt:~/pkgs# ls -l usr/bin/
-rwxr-xr-x    1 root     root        147561 Jun  8 11:00 nano
root@Openwrt:~/pkgs# ./usr/bin/nano --version
 GNU nano, version 9.0
 Compiled options: --enable-tiny --enable-linenumbers --disable-nls --disable-utf8
root@Openwrt:~/pkgs# ./usr/bin/nano
    ----- "nano" runs OK ------

Good progress! You were very close in your second attempt. Two things to fix:

1. The cp step needs explicit paths

In your second attempt you ran cp -r /etc/apk/repositories.d etc/apk/ from inside /root/pkgs/ — that worked by accident because of the relative path. The correct sequence from anywhere is:

mkdir -p /root/pkgs/etc/apk
mkdir -p /root/pkgs/lib/apk
mkdir -p /root/pkgs/var/cache/apk
mkdir -p /root/pkgs/tmp

cp -r /etc/apk/. /root/pkgs/etc/apk/

apk -p /root/pkgs add --initdb
apk -p /root/pkgs update
apk -p /root/pkgs add nano

2. The post-install errors are expected in this case

The errors you saw:

  • execve: No such file or directory
    ERROR: lib/apk/exec/libgcc1-14.3.0-r5.post-install: exited with error 127

These happen because the post-install scripts try to run /bin/sh, which doesn't exist inside the alternate root.

In this particular case, the package files were still installed correctly despite the errors — your third attempt with apk extract confirmed that nano works fine.

So for this specific package, the post-install errors can be ignored.

PS: This should not be generalized to all packages. Package files may be installed successfully despite post-installation errors, but it's important to verify on a case-by-case basis whether the package depends on its post-install script being executed. This is especially true for more complex packages such as system services, databases, or packages that generate caches and runtime configuration.

After installation, add the binaries to your PATH:

export PATH="/root/pkgs/usr/bin:/root/pkgs/usr/sbin:$PATH"
nano --version

Does it work now?

Hello, ncompact.

I updated the previous post, (3 attempts).

Your reply is based purely on guesswork, without any actual testing.

apk -p /root/pkgs/ --initdb add
The command will automatically generate the directory of etc/apk/,lib/apk/db/,var/cache/apk/,var/log/

Regardless of whether the above attempt is successful or not.

We all hope that developers can improve the features of the "apk" and incorporate options like --dest <other/path>, which would be the most user-friendly and convenient for users.

You're right about --initdb automatically creating the directory structure.
I apologize, but I preferred to create the directories manually :sweat_smile:;
I hadn't thought about the command itself doing it automatically. :sweat_smile:

So it can be simplified like this (obviously on my system /mnt is the mount point use yours as a reference if you want to compare the results):

# 1. Initialize the alternate root directory
apk -p /mnt add --initdb

# 2. Copy ONLY the keys and repositories — DO NOT copy world
cp -r /etc/apk/keys /mnt/etc/apk/
cp -r /etc/apk/repositories.d /mnt/etc/apk/

# 3. Update and install
apk -p /mnt update
apk -p /mnt add nano

This sequence installs nano (obviously on /mnt/usr/bin/nano in my case).