Installing a .deb by hand
Use apt for the file on disk, not dpkg -i
Sooner or later something arrives as a .deb rather than as a package in the archive: a vendor's
download page, a colleague's build, a package fetched by hand from a newer Debian. Two commands
will install it. Only one of them installs what it depends on, and the other leaves a state that
is easy to mistake for a broken system.
The examples below use hello-tips_1.0-1_all.deb, a package built for this page. It depends on
cowsay, which is not installed here, and that is the only reason the two commands behave
differently at all.
Read the package before you trust it
A .deb is an archive of two halves: the files to unpack, and a set of maintainer scripts dpkg
runs around the unpacking. Those scripts run as root, with no sandbox and no confirmation, so a
package from outside Debian's archive deserves a minute before it goes on.
dpkg -I prints the control information, and its first few lines list what is in the control
half:
dpkg -I hello-tips_1.0-1_all.deb | head -6
new Debian package, version 2.0.
size 976 bytes: control archive=516 bytes.
320 bytes, 10 lines control
66 bytes, 3 lines * postinst #!/bin/sh
Package: hello-tips
Version: 1.0-1
The * marks a script that will be executed. This package has a postinst, which dpkg runs
after unpacking, and you can read it without installing anything:
dpkg-deb --info hello-tips_1.0-1_all.deb postinst
#!/bin/sh
set -e
echo "hello-tips: postinst running as $(id -un)"
preinst, prerm and postrm are the other names to look for. dpkg -c covers the files themselves,
listing every path the package would place and the mode it would place it with:
dpkg -c hello-tips_1.0-1_all.deb
drwxr-xr-x root/root 0 2026-08-26 15:10 ./
drwxr-xr-x root/root 0 2026-08-26 15:10 ./usr/
drwxr-xr-x root/root 0 2026-08-26 15:10 ./usr/bin/
-rwxr-xr-x root/root 53 2026-08-26 15:10 ./usr/bin/hello-tips
A package writing to somewhere you did not expect shows up here, before it has written anything.
Install it with apt, and mind the ./
apt install hello-tips_1.0-1_all.deb 2>&1 | tail -1
Error: Unable to locate package hello-tips_1.0-1_all.deb
That message sends a lot of people looking for a missing repository. Without a / in it, the
argument is a package name, and apt has correctly reported that no package is called
hello-tips_1.0-1_all.deb. A leading ./ makes it a path, and apt then treats the file as a
one-package source it can resolve against:
apt-get install -s ./hello-tips_1.0-1_all.deb | tail -7
The following NEW packages will be installed:
cowsay hello-tips
0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded.
Inst cowsay (3.03+dfsg2-8 Debian:13.6/stable [all])
Inst hello-tips (1.0-1 local-deb [all])
Conf cowsay (3.03+dfsg2-8 Debian:13.6/stable [all])
Conf hello-tips (1.0-1 local-deb [all])
-s simulates, which is worth doing first on anything you did not build yourself. The plan names
where each package comes from: cowsay from Debian, and hello-tips from local-deb, apt's
name for the file you handed it. Dropping -s carries the plan out:
apt-get install -y ./hello-tips_1.0-1_all.deb 2>&1 | grep -E '^(Unpacking|Setting up|hello-tips)'
Unpacking cowsay (3.03+dfsg2-8) ...
Unpacking hello-tips (1.0-1) ...
Setting up cowsay (3.03+dfsg2-8) ...
Setting up hello-tips (1.0-1) ...
hello-tips: postinst running as root
The last line is the postinst from further up, reporting who ran it.
What dpkg -i does instead
dpkg is the layer beneath apt and has no idea what is in the archive, so it cannot fetch a dependency. It unpacks the package anyway and then refuses to configure it:
dpkg -i hello-tips_1.0-1_all.deb
Selecting previously unselected package hello-tips.
(Reading database ... 12323 files and directories currently installed.)
Preparing to unpack hello-tips_1.0-1_all.deb ...
Unpacking hello-tips (1.0-1) ...
dpkg: dependency problems prevent configuration of hello-tips:
hello-tips depends on cowsay; however:
Package cowsay is not installed.
dpkg: error processing package hello-tips (--install):
dependency problems - leaving unconfigured
Errors were encountered while processing:
hello-tips
The package is now in a state dpkg has a name for:
dpkg -i hello-tips_1.0-1_all.deb >/dev/null 2>&1
dpkg -l hello-tips | tail -1
hello-tips
iU hello-tips 1.0-1 all Demonstration package for installing a .deb by hand
/usr/bin/hello-tips: 2: exec: /usr/games/cowsay: not found
iU reads as desired-Install, status-Unpacked. The files are on the disk and on your PATH,
the postinst never ran, and running the command gets you an error about something else
entirely.
apt can finish the job, because it does know what is in the archive:
dpkg -i hello-tips_1.0-1_all.deb >/dev/null 2>&1
apt-get install -f -y 2>&1 | grep -E '^(Setting up|hello-tips)'
Setting up cowsay (3.03+dfsg2-8) ...
Setting up hello-tips (1.0-1) ...
hello-tips: postinst running as root
-f is --fix-broken. It is the standard recovery from any interrupted or half-finished
install rather than something specific to this one, which makes it the first thing to try when
apt starts refusing to do anything at all.
Afterwards it is an ordinary package, with one gap
Once installed, the package behaves like any other. It appears in dpkg -l, apt remove and
apt purge work on it, and apt autoremove will offer to take its dependencies away with it:
apt-get install -y ./hello-tips_1.0-1_all.deb >/dev/null 2>&1
apt-get purge -y hello-tips 2>&1 | grep -E '^(Removing|Purging)'
Removing hello-tips (1.0-1) ...
The gap is upgrades:
apt-get install -y ./hello-tips_1.0-1_all.deb >/dev/null 2>&1
apt-cache policy hello-tips
hello-tips:
Installed: 1.0-1
Candidate: 1.0-1
Version table:
*** 1.0-1 100
100 /var/lib/dpkg/status
One source, and it is dpkg's own record of what is installed. No repository offers this package,
so apt upgrade has nothing to compare 1.0-1 against and will never mention it again. A security
fix released by the vendor reaches you when you go and look for it.
A vendor that publishes an apt repository instead of a download link is solving exactly this,
which is why it is worth checking whether one exists before downloading the .deb at all. Setting one
up is covered in third-party repositories. Where there is no
repository, the package is yours to keep track of, so it is worth writing down somewhere what you
installed by hand and where it came from.
When the package is not for this machine
Two mismatches account for most failures with a downloaded .deb, and neither is worth fighting.
If the package was built for a different processor, dpkg refuses it outright with package architecture (...) does not match system (...). Vendors usually publish one file per
architecture and the download page picks by browser, which guesses wrong often enough to be
worth checking. dpkg --print-architecture says what this machine wants.
A .deb built for a different Debian release fails later and less obviously: the architecture
matches and the dependencies do not. A package from a newer Debian asks for library versions this
release does not carry, and
apt will decline to install it rather than pull half of the next release in behind it. Forcing
past that with dpkg -i --force-depends produces exactly the iU state above, with no apt
command that can rescue it.