Miss a leading space on one continuation line and your Description field quietly falls apart into separate lines instead of one readable paragraph. This guide creates the metadata file inside a Debian package, checks its fields with dpkg-deb, and builds a package archive without touching the system package database. Allow about fifteen minutes. You need the installed dpkg-dev tools and an ordinary shell; the examples use dpkg-dev 1.22.6ubuntu6.6 and follow the deb-control(5) format installed with that package.
This builds a harmless package containing one documentation file. It installs nothing. Keep the staging directory in a workspace you control, and pick a fresh output filename so shell redirection cannot clobber an existing package.
The binary package control file is called control and lives in the package's DEBIAN directory. Confirm the tool version before you start:
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg-dev
dpkg-dev 1.22.6ubuntu6.6
$ command -v dpkg-deb
/usr/bin/dpkg-deb
Pick a lowercase package name that will not be mistaken for a real package. It becomes part of the archive filename and the value of the required Package field; this example uses example-report.
Checkpoint: build a private staging tree. These commands create directories and one small text file, and make no system-wide change:
$ work=/tmp/example-report-package
$ rm -rf -- "$work"
$ mkdir -p "$work/DEBIAN" "$work/usr/share/doc/example-report"
$ printf '%s\n' 'Example package content.' > "$work/usr/share/doc/example-report/README"
$ test -f "$work/usr/share/doc/example-report/README" && echo 'staging ready'
staging ready
That rm -rf line is deliberately scoped to the one exact temporary path. Do not adapt it to a broader directory, or to a variable whose value you have not actually checked.
A field starts with a tag, a colon and its value. Tags are case insensitive, but field values generally are not. For a normal binary package, the required fields are Package, Version, Architecture and Description. Package-Type defaults to deb when it is absent, so skip it for an ordinary package.
Write a real Debian version string, not an arbitrary display label. The packaging revision after the hyphen is optional for a native package, but 1.0-1 makes a clear example:
$ cat > "$work/DEBIAN/control" <<'EOF'
Package: example-report
Version: 1.0-1
Architecture: all
Maintainer: Example Maintainer <[email protected]>
Section: misc
Priority: optional
Description: Small example report package
A package used to demonstrate Debian control metadata.
It contains one documentation file and has no service to start.
EOF
$ sed -n '1,12p' "$work/DEBIAN/control"
Package: example-report
Version: 1.0-1
Architecture: all
Maintainer: Example Maintainer <[email protected]>
Section: misc
Priority: optional
Description: Small example report package
A package used to demonstrate Debian control metadata.
It contains one documentation file and has no service to start.
The short description is the text on the Description line itself. Every long-description line has to begin with one space, that is the whole rule from the opening line. A blank paragraph is written as a line holding one space and a full stop, never an actually empty line. Do not indent ordinary fields, and do not add comments unless the tool reading this file explicitly supports them.
Dependency fields change how package-management tools resolve installation and removal. Start with none if the files do not need any. When a dependency is real, use Depends for packages required for useful operation, and reach for Pre-Depends only when the package must already be installed and configured first, commonly for a pre-installation script.
Commas separate groups with AND semantics. A vertical bar separates alternatives with OR semantics, and alternatives bind more tightly than commas. A package can carry an architecture qualifier and a version relation:
Depends: ca-certificates, curl | wget, libexample:any (>= 2.4)
An omitted architecture qualifier means the current binary package's own architecture in these fields. any only means something when the named package permits foreign-architecture use through its Multi-Arch metadata. The accepted version relations are >>, <<, >=, <= and =. Do not add a dependency just because a command happened to be present on your build machine.
For this documentation-only example, leave Depends out entirely. That keeps the generated package usable without making a claim about runtime requirements it cannot back up.
Architecture: all is for architecture-independent content such as documentation, shell scripts and many Perl scripts. Use a concrete architecture such as amd64 when the package actually contains binaries compiled for it. Check the value instead of guessing:
$ dpkg --print-architecture
amd64
Section categorises the package and Priority describes its importance to the distribution; their accepted values normally come straight from distribution policy. Maintainer is recommended and usually holds a name followed by an email address. Homepage, Origin, Bugs and Tag add useful metadata, but none is required by the control-file format for this example.
Warning: do not mark an ordinary package Essential: yes or Protected: yes to make it look more important than it is. Both fields affect removal safeguards on every system that installs the package. Protected has been supported since dpkg 1.20.1, and each should be reserved for packages that genuinely need that level of system protection.
dpkg-deb inspects a package archive, not the unbuilt staging directory, so build to a new path first. This validates the control file and creates an archive without installing it:
$ output=/tmp/example-report_1.0-1_all.deb
$ dpkg-deb --build "$work" "$output"
dpkg-deb: building package 'example-report' in '/tmp/example-report_1.0-1_all.deb'.
If the command reports a malformed field, fix DEBIAN/control and build again to another new destination. The usual culprits are a missing colon, a misspelled required tag, an unindented long-description line, or an invalid version string.
Checkpoint: inspect the fields in the archive you just built:
$ dpkg-deb --info "$output"
new Debian package, version 2.0.
size ... bytes: control archive=... bytes.
Package: example-report
Version: 1.0-1
Architecture: all
Maintainer: Example Maintainer <[email protected]>
Section: misc
Priority: optional
Description: Small example report package
A package used to demonstrate Debian control metadata.
It contains one documentation file and has no service to start.
Byte counts vary, so check the package identity and fields rather than copying those numbers into a test.
Now check the file list and selected fields. These are ordinary, unprivileged operations:
$ output=/tmp/example-report_1.0-1_all.deb
$ dpkg-deb --field "$output" Package Version Architecture
example-report
1.0-1
all
$ dpkg-deb --contents "$output"
... ./usr/share/doc/example-report/README
Output wording and file metadata can differ between dpkg versions. What actually matters is a successful exit status, the expected package fields, and the expected path in the archive.
Warning: do not run dpkg -i just to test the archive. Installation changes the system and can trigger maintainer scripts or dependency resolution. If you install a test package by mistake, remove it with sudo dpkg -r example-report after checking it is not needed elsewhere. This guide's package has no maintainer scripts, but removal is still a privileged state change.
If the package name in dpkg-deb --field is wrong, check that there is exactly one Package: field and that its value holds the name you intended. If the long description shows up as separate fields or loses its paragraphs, check that every continuation line starts with one space and that a blank paragraph is written as .. If an architecture-independent package is labelled amd64, only change it to all once every packaged file really is architecture independent.
If a dependency is rejected, check the package name and version relation against the target distribution. A successful build does not prove the dependency can be satisfied in every repository. Recommends and Suggests express progressively weaker relationships, while Breaks and Conflicts can stop packages being configured or installed together: use those fields only for a relationship you have actually tested.
Once the metadata is correct, keep the control file with the package source. You can remove the exact temporary tree and archive when you are finished:
$ rm -rf -- "$work" "$output"
$ test ! -e "$work" && test ! -e "$output" && echo 'temporary package removed'
temporary package removed
. used for any blank paragraph.