Manage Debian conffiles Without Upgrade Surprises
List a file in DEBIAN/conffiles that is not actually in the package, and dpkg will quietly ignore the whole declaration rather than warn you. This guide builds a package whose control archive contains a valid DEBIAN/conffiles file, checks the result, and covers when the remove-on-upgrade flag is the right call. Allow about fifteen minutes if you already have a package skeleton. The examples use dpkg 1.22.6 and dpkg-dev 1.22.6 on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is a packaging exercise, not a way to edit /etc on an installed system. Build in a temporary directory or package workspace. None of the commands below need sudo; save elevated privileges for a later, deliberate installation step.
1. Check the installed packaging tools
Confirm the version and the command you will use to inspect the archive:
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev dpkg
dpkg 1.22.6ubuntu6.6
dpkg-dev 1.22.6ubuntu6.6
$ command -v dpkg-deb
/usr/bin/dpkg-deb
The installed manual describes DEBIAN/conffiles as a list stored in the package's control archive. It gets read while creating and processing a package; it is not a file that lives under the package's ordinary data directory.
2. Create the package layout
Start with a minimal package tree. Replace /tmp/example-conffile with a private workspace if you plan on keeping the source:
$ mkdir -p /tmp/example-conffile/pkg/DEBIAN
$ mkdir -p /tmp/example-conffile/pkg/etc/example
The control file needs the normal package metadata. Keep this one deliberately small:
$ editor /tmp/example-conffile/pkg/DEBIAN/control
Package: example-conffile
Version: 1.0
Architecture: all
Maintainer: Example <[email protected]>
Description: example package with a configuration file
Create the configuration file at the same absolute path you are about to declare. This is the trap from the opening line: a normal, unflagged conffile has to exist in the binary package, or dpkg silently ignores the declaration instead of erroring out:
$ editor /tmp/example-conffile/pkg/etc/example/app.conf
mode=standard
3. Write one absolute path per line
Put the declaration in pkg/DEBIAN/conffiles. Paths must be absolute. Each line can carry an optional leading flag separated from the path by whitespace; do not use shell quoting or a package-relative path here:
$ editor /tmp/example-conffile/pkg/DEBIAN/conffiles
/etc/example/app.conf
Trailing whitespace gets trimmed, but an empty line or a line of only whitespace is not accepted. Keep the file boring: one declaration per line, no comments unless your packaging tooling explicitly supports them. A comment is not documented conffiles syntax, whatever your editor's highlighting suggests.
Checkpoint
Inspect the file before building:
$ sed -n 'l' /tmp/example-conffile/pkg/DEBIAN/conffiles
/etc/example/app.conf$
The trailing $ shown by sed is its own line marker, not part of the file.
4. Build and inspect the archive
Build the package as an ordinary user:
$ dpkg-deb --build /tmp/example-conffile/pkg /tmp/example-conffile/example-conffile.deb
dpkg-deb: building package 'example-conffile' in '/tmp/example-conffile/example-conffile.deb'.
Inspect the control metadata without installing anything:
$ dpkg-deb --info /tmp/example-conffile/example-conffile.deb
new Debian package, version 2.0.
size ... bytes: control archive=... bytes.
22 bytes, 1 lines conffiles
Package: example-conffile
Version: 1.0
Architecture: all
...
Sizes and displayed metadata vary. The checkpoint that matters is the conffiles entry along with the package name and version. Read the stored file directly if you need to check the exact bytes:
$ dpkg-deb --ctrl-tarfile /tmp/example-conffile/example-conffile.deb \
| tar -xOf - ./conffiles
/etc/example/app.conf
Also confirm the data archive actually ships the declared file:
$ dpkg-deb --contents /tmp/example-conffile/example-conffile.deb
drwxr-xr-x ... ./etc/
drwxr-xr-x ... ./etc/example/
-rw-r--r-- ... ./etc/example/app.conf
5. Mark a retired conffile for removal
Use the only flag the installed manual documents for a file that should go on the next upgrade:
remove-on-upgrade /etc/example/old-app.conf
That entry behaves differently from an ordinary conffile: the old file must be absent from the new binary package. Do not leave a placeholder at pkg/etc/example/old-app.conf, since dpkg and dpkg-deb reject a package that both marks a path for removal and ships it anyway.
Destructive action
This flag deletes a file from every system that installs the upgrade. Before publishing it, confirm the path is genuinely obsolete and note down where any replacement configuration now lives. Do not use it just to force a user's edited config back to a vendor default. Build the package, then check both the control entry and the data listing again:
$ dpkg-deb --info /tmp/example-conffile/example-conffile.deb | sed -n '/conffiles/,+2p'
... bytes, 1 lines conffiles
$ dpkg-deb --contents /tmp/example-conffile/example-conffile.deb \
| grep -F '/etc/example/old-app.conf'
$ printf 'grep status: %s\n' "$?"
grep status: 1
A status of 1 from that final grep means the retired path was not shipped. It does not install the package, and it does not prove an upgrade will go smoothly: test the package on a disposable system before it reaches production.
Common traps
- A relative path such as
etc/example/app.confis invalid. Use/etc/example/app.conf. - An ordinary declaration with no matching data file may be silently ignored by dpkg. Check both control metadata and archive contents.
- An empty or whitespace-only line is not accepted. Remove blank declarations instead of trusting the trim.
- The flag is not a general deletion mechanism. The only documented one is
remove-on-upgrade, and it fires on the next upgrade only. - Do not install a package just to inspect it.
dpkg-deb --info,--contentsand--ctrl-tarfileare read-only archive checks.
If you built a test package under /tmp, remove that workspace once you have saved anything you need. If you never installed the package, there is no package-state change to undo. If you did install it separately, follow your normal package rollback plan: deleting the build directory will not uninstall it for you.
Done means
- DEBIAN/conffiles contains valid absolute paths, one per line.
- Every ordinary declaration matches a file that is actually in the binary package.
- Any remove-on-upgrade path is absent from the binary package and reviewed as a deliberate removal.
- dpkg-deb --info, --contents and the control-archive check show the package you intended.
- The package has been tested before any elevated installation or production upgrade.