You have spent four modules typing sudo apt install without asking where that software comes from, who guarantees nobody tampered with it along the way, or what happens if one night Tramontana's database engine jumps from version 15 to 16 and the application stops starting. Now that you know exactly what sudo lets you do, it is time to understand what you do with it most of the time: install, update and pin software. This lesson covers Debian and Ubuntu's two layers, the cryptographic signing of repositories, version pinning and the uncomfortable debate about whether a server should reboot itself after a security update.
Contents
- What a package manager solves
- Anatomy of a
.deband a comparison with.rpm - The two layers:
dpkgversusapt aptday to daydpkgand file archaeology- Repositories: deb822, components, pockets and keys
- Pinning versions:
holdand pinning - Unattended upgrades, the reboot and cleaning the cache
- Snap, Flatpak and AppImage versus
.deb - Compiling from source when there is no alternative
- Equivalents with
dnfon RHEL - Tramontana case: dependencies, a pinned version and an audit
- What a package manager solves
Installing software "by hand" — downloading a .tar.gz, compiling it and copying it into /usr/local — looks harmless until the first security update arrives. A package manager solves four problems that have no reasonable manual solution:
- Dependencies: it knows your application needs
libssl3t64and that another twenty programs use that library, so it installs it once and shares it. - Security updates: one command updates everything installed. With hand-compiled software, the day a critical CVE comes out you will have to remember it yourself.
- Integrity and provenance: every package comes signed and its checksum verified. You know that what gets installed is what the maintainer published.
- Clean uninstallation: the manager knows exactly which files each package brought. A
make installdoes not even leave the list.
An operational rule: compiling is the last resort, and when you have to, do it in /usr/local so as not to tread on what the manager administers.
- Anatomy of a
.deb and a comparison with .rpm
.deb and a comparison with .rpmA .deb is an ar archive with three pieces: debian-binary (the format version), control.tar.zst (metadata and scripts) and data.tar.zst (the files that get installed).
$ apt-get download tree >/dev/null && dpkg-deb --info tree_*.deb | sed -n '2,7p'
Package: tree
Version: 2.1.1-2ubuntu3
Depends: libc6 (>= 2.38)
Installed-Size: 133
Maintainer: Ubuntu Developers <[email protected]>
Description: displays an indented directory listingThe inter-package relationship fields are the ones doing all the work:
| Field | Meaning |
|---|---|
Depends |
Essential: without it, the package does not work |
Recommends / Suggests |
Dispensable but installed anyway (--no-install-recommends) / purely informative |
Conflicts / Breaks / Replaces / Provides |
Cannot coexist / replaces another one or supplies a generic capability |
Inside control there are also maintainer scripts — preinst, postinst, prerm, postrm — which run as root at each phase. That is exactly why adding a third-party repository is a security decision and not a convenience: installing a package means running somebody else's code as root.
| Aspect | .deb (Debian/Ubuntu) |
.rpm (RHEL/Fedora/SUSE) |
|---|---|---|
| Low / high level | dpkg / apt |
rpm / dnf (formerly yum) |
| Metadata | A control file |
A binary header and a .spec at build time |
| Configuration | conffiles, asks when merging |
.rpmnew / .rpmsave |
| Database | /var/lib/dpkg/status |
/var/lib/rpm |
- The two layers:
dpkg versus apt
dpkg versus aptflowchart LR
R[Repositories<br/>archive.ubuntu.com] -->|downloads and verifies signature| A
A[apt: resolves dependencies] -->|invokes| D[dpkg: installs ONE file]
L[A .deb downloaded by hand] --> D
D --> S[(/var/lib/dpkg)]
D --> F[Files on the system]
dpkg knows how to install one .deb file and keep the books on what is installed; it knows nothing about repositories and downloads no dependencies. apt knows how to talk to the repositories, resolve the dependency graph, verify signatures and decide the order, and then it delegates to dpkg.
| You need to… | Command |
|---|---|
| Install by name from the repository | apt install package |
Install a local .deb with dependencies |
apt install ./file.deb |
Install a local .deb without resolving anything |
dpkg -i file.deb (then apt -f install) |
| File → package / package → files | dpkg -S /path / dpkg -L package |
A note: apt is the interface for humans and apt-get/apt-cache are the stable ones for scripts. In a script use apt-get: apt warns that its interface may change between versions.
apt day to day
apt day to daysudo apt update # refreshes the available LIST. It updates NOTHING installed
sudo apt upgrade # updates what is installed without removing or installing new packages
sudo apt full-upgrade # the same, but it MAY remove packages to resolve the changeThe classic confusion: update only downloads the indexes. Without it, apt works from an old snapshot of the repository and will not see the security update published this morning. The difference between upgrade and full-upgrade matters on a server: upgrade is conservative and would rather touch nothing than delete something; full-upgrade accepts removals and is what you need for a distribution version change.
sudo apt install nginx=1.24.0-2ubuntu7 # a specific version from the repository
sudo apt remove nginx # deletes the binaries, KEEPS the configuration in /etc
sudo apt purge nginx # also deletes the configuration
sudo apt autoremove --purge # removes dependencies nobody needs any moreremove versus purge is a real decision: remove lets you reinstall and get your configuration back; purge leaves the system clean. On a server that is documented, purge stops a ghost /etc/whatever turning up in a year's time that nobody knows the origin of.
Querying:
apt search 'web server' # searches the name and the description
apt show nginx # description, dependencies, size, origin
apt list --installed | wc -l # how many packages are installed
apt list --upgradable # what is pending an update
apt depends nginx; apt rdepends libssl3t64 # direct and reverse dependencies
apt download tree # downloads the .deb without installing itAnd the option that saves the most disk on a server is sudo apt install --no-install-recommends nginx. The Recommends drag in extras designed for the desktop (documentation, graphical clients, complete mail servers). On a server, every extra package is attack surface and occupied disk. You can make it permanent:
# /etc/apt/apt.conf.d/99no-recommends
APT::Install-Recommends "false";
APT::Install-Suggests "false";
dpkg and file archaeology
dpkg and file archaeology$ dpkg -l | grep -c '^ii' # 712 packages correctly installed
$ dpkg -S /usr/bin/ss # which package did this file come from?
iproute2: /usr/bin/ssThe states in dpkg -l are read as two letters: the first is the desired state and the second the actual one. ii is "installed and installed"; rc is "removed but configuration files remain" (candidates for purge); iU or iF indicate a half-finished installation, which is fixed with sudo dpkg --configure -a (it finishes interrupted configurations) and sudo apt -f install (it repairs broken dependencies).
To look for a file in packages you do not have installed:
$ sudo apt install apt-file && sudo apt-file update && apt-file search /usr/bin/mkfs.xfs
xfsprogs: /usr/bin/mkfs.xfs
- Repositories: deb822, components, pockets and keys
Ubuntu 24.04 abandoned the one-line format of /etc/apt/sources.list and uses deb822, which is far more readable, in /etc/apt/sources.list.d/*.sources:
# /etc/apt/sources.list.d/ubuntu.sources
Types: deb
URIs: http://es.archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpgThe file carries a second, identical block with URIs: http://security.ubuntu.com/ubuntu/ and Suites: noble-security, which is the one that brings the patches.
| Component | What it contains | Support |
|---|---|---|
main / restricted |
Free software maintained by Canonical / proprietary drivers | Yes, with patches |
universe / multiverse |
Maintained by the community / with licence restrictions | "Best effort" (Ubuntu Pro extends it) / no |
| What it is | In production? | |
|---|---|---|
noble |
The version frozen on release day | Yes |
noble-security |
Security patches | Yes, always |
noble-updates |
Fixes for non-critical bugs | Yes, the usual choice |
noble-backports |
Newer versions brought back from later releases | Only occasionally |
The signature: why apt-key is obsolete
Every repository signs its Release index with a GPG key. In the past all the keys went into a global keyring (apt-key add), which meant that any third-party repository could sign any package, including a fake openssh-server that replaced the official one. That is why apt-key is obsolete and has been removed in 24.04. Today the key is stored in a file and tied to that repository with Signed-By:.
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://example.test/key.asc | sudo tee /etc/apt/keyrings/example.asc >/dev/null# /etc/apt/sources.list.d/example.sources
Types: deb
URIs: https://example.test/apt/
Suites: noble
Components: main
Signed-By: /etc/apt/keyrings/example.ascAdding a third-party repository is a security decision. You are authorising a third party to run code as root on your server every time you update, and to replace packages if you do not limit their scope. Before adding it: check who publishes it, use HTTPS, tie the key with
Signed-By, restrictComponentsto what is needed and, if you really care, apply pinning so that it can only supply the packages you intend. In a corporate environment this decision must be validated by the security officer.
Ubuntu's PPAs are Launchpad repositories that sudo add-apt-repository ppa:user/project adds along with their key. Convenient on the desktop; in production, every PPA is an individual maintainer you become dependent on.
- Pinning versions:
hold and pinning
hold and pinningThere is software that cannot update itself. Tramontana's database engine is the textbook case: a major version jump means migrating the data format, and doing that at 06:00 unsupervised means an application that is down and a complicated rollback.
The simple way, with dpkg:
$ sudo apt-mark hold postgresql-16 postgresql-client-16
postgresql-16 set on hold.
$ apt-mark showhold
postgresql-16
postgresql-client-16
$ sudo apt-mark unhold postgresql-16 # when updating: by hand and with a windowA package on hold is not touched by upgrade, nor by full-upgrade, nor by unattended-upgrades.
The more controlled way, APT pinning, lets you decide which origin each thing comes from:
# /etc/apt/preferences.d/99-tramontana-bd — the DB stays on the 16 series
Package: postgresql-16 postgresql-client-16 postgresql-common
Pin: version 16.*
Pin-Priority: 1001| Priority | Effect |
|---|---|
| < 0 / 100 | Never installed / only if nothing is installed |
| 500 / 990 | Normal for a repository / that of the target release |
| > 1000 | Installed even if it means downgrading |
$ apt-cache policy postgresql-16
postgresql-16:
Installed: 16.3-1.pgdg24.04+1
Candidate: 16.3-1.pgdg24.04+1
17.0-1.pgdg24.04+1 500 # it exists, but it does not win
*** 16.3-1.pgdg24.04+1 1001apt-cache policy is the tool that confirms your pinning does what you think. Always check it: a badly written pin gives no error, it simply does not apply.
- Unattended upgrades, the reboot and cleaning the cache
A server without security patches is only a matter of time. Ubuntu includes unattended-upgrades, which by default installs only what comes from the -security pocket:
# /etc/apt/apt.conf.d/50unattended-upgrades (the relevant fragment)
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Package-Blacklist { "postgresql-16"; };
Unattended-Upgrade::Mail "[email protected]";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Automatic-Reboot "false";The automatic-reboot debate has no universal answer. A kernel or glibc update has no effect until you restart the affected processes or the machine:
$ cat /var/run/reboot-required.pkgs 2>/dev/null
linux-image-6.8.0-45-generic
$ sudo needrestart -b # which services are using already-updated libraries
NEEDRESTART-SVC: tramontana.service| Position | In favour | Against |
|---|---|---|
Automatic-Reboot "true" |
The patch takes effect without depending on anybody | An unannounced outage; if the app does not start on its own, a long one |
"false" + a warning |
It is rebooted in an agreed window | If nobody reads the warning, the patch protects nothing |
For Tramontana, with a single server and no high availability (which is the subject of 07-07), the reasonable decision is false plus an alert Marta receives, plus a written commitment to a weekly window. And the indispensable habit: a VM snapshot before a big update, which on a real server is an LVM or hypervisor snapshot (05-04).
And the cleanup, which on a server with /var on its own partition — like the one you partitioned in 01-04 — prevents a full disk: du -sh /var/cache/apt/archives usually gives hundreds of megabytes, which sudo apt clean deletes entirely and apt autoclean only for what has already been withdrawn from the repository; old kernels are cleaned by apt autoremove --purge.
- Snap, Flatpak and AppImage versus
.deb
.deb| Format | Dependencies | Updating | On a server |
|---|---|---|---|
.deb |
Shared with the system | apt, controlled |
The default option |
snap |
Bundled | Automatic and hard to prevent | Only if there is no .deb: the auto-update disrupts change windows |
flatpak |
Shared runtimes | Manual or scheduled | Designed for the desktop |
| AppImage | Everything inside one file | None: you do it yourself | A standalone tool; nobody patches it for you |
Ubuntu ships snapd as standard, and some pieces (such as lxd or certbot) are distributed that way. What you need to know on a server: snaps update on their own and their pace can be postponed but not cancelled, which clashes head-on with a controlled-change policy.
- Compiling from source when there is no alternative
sudo apt install build-essential # gcc, make, libc6-dev, dpkg-dev
tar -tzvf tool-2.4.tar.gz | head # inspect BEFORE extracting, as always
tar -xzf tool-2.4.tar.gz && cd tool-2.4
./configure --prefix=/usr/local && make -j"$(nproc)" && sudo make install--prefix=/usr/local is not a detail: the FHS from 01-06 reserves that tree for what the administrator installs, precisely so that it never collides with what dpkg manages in /usr. And since make install leaves no record, checkinstall exists, wrapping the installation in a real .deb:
$ sudo checkinstall --pkgname=tool --pkgversion=2.4 --default
$ dpkg -l tool | tail -1
ii tool 2.4-1 amd64 Package created with checkinstallNow it uninstalls cleanly with apt purge tool. Even so, there is still nobody to warn you about its security flaws: that watch becomes yours.
- Equivalents with
dnf on RHEL
dnf on RHELPicking up the families from 01-03, this table lets you work on Rocky, Alma or RHEL without learning anything again:
| Task | Debian/Ubuntu | RHEL/Fedora |
|---|---|---|
| Refresh / update | apt update / apt upgrade |
dnf check-update / dnf upgrade |
| Install / remove | apt install / apt purge |
dnf install / dnf remove |
| Search / detail | apt search / apt show |
dnf search / dnf info |
| File→package / package→files | dpkg -S / dpkg -L |
rpm -qf / rpm -ql |
| Pin a version / history | apt-mark hold, history.log |
dnf versionlock, dnf history |
The most striking practical difference: dnf history undo reverses an entire transaction, something APT does not offer. On Debian/Ubuntu you go back by reinstalling specific versions worked out from history.log.
- Tramontana case: dependencies, a pinned version and an audit
First, the server's dependencies, with no recommends and with verification:
sudo apt update && sudo apt install --no-install-recommends -y \
nginx postgresql-16 postgresql-client-16 rsync curl jq acl logrotateSecond, we pin the database so that no unattended upgrade raises its major version:
sudo cp -a /etc/apt/apt.conf.d/50unattended-upgrades{,.bak-$(date +%F)}
sudo apt-mark hold postgresql-16 postgresql-client-16And we add the explicit pinning in /etc/apt/preferences.d/99-tramontana-bd that you saw in section 7, plus the blacklist in 50unattended-upgrades. A mandatory verification:
$ apt-cache policy postgresql-16 | head -3
postgresql-16:
Installed: 16.3-0ubuntu0.24.04.1
Candidate: 16.3-0ubuntu0.24.04.1 # identical: nothing pending that could slip throughThird, Marta's question this morning: "what was updated last night and why?".
$ grep -A3 '^Start-Date: 2026-08-18' /var/log/apt/history.log
Start-Date: 2026-08-18 06:12:33
Commandline: /usr/bin/unattended-upgrade
Upgrade: libssl3t64:amd64 (3.0.13-0ubuntu3.2, 3.0.13-0ubuntu3.4), openssl:amd64 (...)
End-Date: 2026-08-18 06:12:51
$ apt changelog openssl 2>/dev/null | head -2
openssl (3.0.13-0ubuntu3.4) noble-security; urgency=medium
* SECURITY UPDATE: denial of service in TLS session handlingThe answer for Marta, with the usual structure — what it protects and what it does not protect —: "Last night OpenSSL was updated because of a security flaw that allowed the TLS service to be brought down. It protects the encryption of incoming connections. It protects nothing else, and it will not take full effect until we restart the services that already had the library loaded; needrestart says tramontana.service is one of them. I propose restarting it in Thursday's window. The database is still pinned to the 16 series and has not been touched." And into the HISTORY:
sudo tee -a /opt/tramontana/HISTORY >/dev/null <<'END'
2026-08-18 Packages (operator)
- hold + pin of postgresql-16 (16.* series); unattended-upgrades only -security
- 06:12 automatic update of openssl (TLS CVE). Service restart still pending.
ENDCommon Mistakes and Tips
- Believing that
apt updateupdates anything. It only refreshes the list; the correct pair isapt update && apt upgrade, and the&&is not decorative: if theupdatefails, upgrading with old indexes is worse than not upgrading. - Using
aptin scripts instead ofapt-getwithDEBIAN_FRONTEND=noninteractive, or plaindpkg -i, which installs without dependencies and leaves the system in theiFstate (useapt install ./file.deb). - Adding a repository with
apt-key. Removed in 24.04 and insecure by design: the key goes in/etc/apt/keyrings/andSigned-By:in the.sources. - Forgetting the
-securitypocket. Without it the server goes unpatched, and it gives no error at all. And do not update without a snapshot or a window: the day something breaks, the difference between 5 minutes and 5 hours is having taken the snapshot. - Leaving a
holdin place and forgetting it. A held package does not receive security patches either: reviewapt-mark showholdevery month and write down who decided each hold and until when. - Tip: keep
apt-mark showmanual > packages.txtalongside your backups. Rebuilding a server from that list is a matter of minutes.
Exercises
- Simulate before acting. Find out which packages would be updated right now and how much disk they would take, without installing anything. Then check whether any of them requires a system reboot.
- A third-party repository set up properly. Describe the complete, safe steps to add a fictitious repository
https://apt.tramontana.example/(suitenoble, componentmain) that should only supply thetramontana-agentpackage, and state how you would verify it cannot replace system packages. - The forensics of an update. A colleague says "somebody installed
nginxby hand on Tuesday". Demonstrate with commands when it was installed, whether it came from a repository or from a standalone.deb, and which configuration files it contributes.
Solutions
1.
$ sudo apt update >/dev/null && apt list --upgradable
libssl3t64/noble-security 3.0.13-0ubuntu3.4 amd64 [upgradable from: 3.0.13-0ubuntu3.2]
openssl/noble-security 3.0.13-0ubuntu3.4 amd64 [upgradable from: 3.0.13-0ubuntu3.2]
$ sudo apt-get --simulate upgrade | tail -1
Conf openssl (3.0.13-0ubuntu3.4 ...)
$ sudo apt-get --assume-no upgrade | grep -i space
After this operation, 0 B of additional disk space will be used.--simulate (or -s) is APT's --dry-run, and it fits the course's convention of simulating before acting. For the reboot: [ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs || echo "No reboot required".
2. The steps, in order:
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://apt.tramontana.example/key.asc | sudo tee /etc/apt/keyrings/tramontana.asc >/dev/null
sudo chmod 0644 /etc/apt/keyrings/tramontana.asc# /etc/apt/sources.list.d/tramontana.sources
Types: deb
URIs: https://apt.tramontana.example/
Suites: noble
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/tramontana.asc# /etc/apt/preferences.d/99-repo-tramontana
Package: *
Pin: origin apt.tramontana.example
Pin-Priority: 1
# ...except the intended package:
Package: tramontana-agent
Pin: origin apt.tramontana.example
Pin-Priority: 700Verification: sudo apt update must give no signature warnings, and apt-cache policy openssl must still show noble-security as the candidate's origin. That priority-1 pin is the safety net that stops a third-party repository hijacking a system package.
3.
$ grep -B1 -A2 'Install:.*nginx' /var/log/apt/history.log
Start-Date: 2026-08-13 11:47:02
Commandline: apt install nginx
Requested-By: luis (1001)
Install: nginx:amd64 (1.24.0-2ubuntu7.1), nginx-common:amd64 (..., automatic)
$ apt-cache policy nginx | sed -n '5p'
500 http://es.archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages
$ dpkg-query -W -f='${Conffiles}\n' nginx-common | head -1
/etc/nginx/nginx.conf 8b1a9953c4611296a827abf8c47804d7It came from the official repository (noble-updates), luis asked for it on 13 August with apt install, and it contributes conffiles that apt will protect in future updates. Note the Requested-By field: history.log records the real UID behind the sudo, which links directly to the traceability of the previous lesson.
Conclusion
The software on srv-tramontana no longer appears by magic. You know that a package manager solves dependencies, updates, integrity and clean uninstallation, and that compiling is the last resort; you know the anatomy of a .deb — including the maintainer scripts that run as root, which is the underlying reason for all the caution around repositories — and its equivalence with .rpm; you tell the dpkg layer from the apt layer and you know when each one applies; you handle update/upgrade/full-upgrade, remove versus purge, --no-install-recommends, depends/rdepends and the archaeology of dpkg -L, dpkg -S and apt-file.
You understand Ubuntu 24.04's repositories in deb822 format, their components and their pockets, and why the key goes in /etc/apt/keyrings/ with Signed-By: instead of the late apt-key. You know how to pin versions with apt-mark hold and with pinning, verify it with apt-cache policy, and that a forgotten hold leaves a package unpatched. You have configured unattended-upgrades so that it installs security only, warns Marta and does not reboot on its own, and you know how to read /var/run/reboot-required and needrestart. And you can place snap, Flatpak and AppImage, compile into /usr/local with checkinstall when you have to, and translate all of it into dnf.
What remains is the resource everything else depends on and that nobody looks at until it runs out: the disk. /srv/tramontana/backups has been growing for weeks, the 04:20 backup writes every night without anybody having worked out how much fits, and the apt cache you have just cleaned was a symptom, not the cause. In Disk Management you will see the full stack from the physical device to the mount point, MBR and GPT partitions, filesystems compared, /etc/fstab field by field — and why one badly written line there stops the server booting —, the case of the deleted file that does not free up space, and LVM, with which you will add a new disk to the VM and move /srv/tramontana/backups to its own volume without losing a single byte.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
