I’ve had several people ask about Bacula because it’s not quite as well known among tech folks unless you’ve ever been tasked to venture out and find a replacement for some horrible proprietary tape library backup software. The options in this case tend to be Bacula (or its fork Bareos) and Amanda. I can’t speak for Amanda/Zmanda other than evaluating it many years ago and being impressed at its capabilities, but we would have wanted the Pro version and I don’t remember what happened with that project. I think the economy was crumbling around us back in 2008 and we got acquired so that put a stop to that backup software replacement project.
People tend to gr…
I’ve had several people ask about Bacula because it’s not quite as well known among tech folks unless you’ve ever been tasked to venture out and find a replacement for some horrible proprietary tape library backup software. The options in this case tend to be Bacula (or its fork Bareos) and Amanda. I can’t speak for Amanda/Zmanda other than evaluating it many years ago and being impressed at its capabilities, but we would have wanted the Pro version and I don’t remember what happened with that project. I think the economy was crumbling around us back in 2008 and we got acquired so that put a stop to that backup software replacement project.
People tend to gravitate towards solutions that are new/trendy, or old but rather simple. Examples:
- rsnapshot (rsync-based, used it for years, I believe still in use at an old job)
- restic (encryption, deduplication, “simple” because it’s a Go binary, but slow)
- borg (like restic, but Python)
- plakar (Go, pretty young, has web interface, unclear if it’s good)
- kopia (another new contender, has desktop GUI for management)
- tarsnap (kinda esoteric, not free to backup to Colin’s cloud, but does work well!)
I’m going to quickly make the case for Bacula and try to keep it as simple as possible because this software is incredibly flexible. If you want to do something that doesn’t seem normally possible it can probably be done with custom commands and SQL that can be embedded in the config files. You get a lot of control over your backup system which can be intimidating or exhilirating – choose your own adventure!
The Bacula Community Edition is more than capable of backing up almost everything the average person is going to want to backup. However, there are additional features offered by Bacula Systems such as plugins for databases, hypervisors, etc. If you want a commercial solution with support and guidance, then this is where to look.
I want to note that Bacula is capable of backing up or restoring to more than just files: FIFO/pipes, raw disks and partitions, and even sparse files are handled gracefully. Most people don’t need this, but it’s a great tool to have in your toolbox.
Bacula’s Anatomy 101
Quick Glossary:
Pool - a logical grouping of Volumes. The Volumes will follow the naming convention defined at the Pool level. e.g., “Database-001”
Volume - the actual storage unit of the data. Originally this would have been a tape, but now it can just be a file on a filesystem or even an object on S3.
Bacula has three main services: the Director, the File Daemon, and the Storage Daemon.
File Daemon
The File Daemon (bacula-fd) is really the backup client. The Director will connect to the File Daemon on port 9102 to tell it to run a job, so this port needs to be accessible. The File Daemon then streams its data to the Storage Daemon as defined by the Job it received to run.
It’s a tiny binary weighing in around ~225K on my laptop. When running a backup it’s consuming a couple tens of megabytes of RAM only which is impressive considering how fast and capable it is, as well as supporting compression, deduplication*, and encrypted network communications.
* I’ve never used the deduplication functionality which as documented can be done at the client, storage, or both. I don’t know how expensive it is to use it. I figure most people would prefer to have the storage side deduplicate at the filesystem level anyway.
Director
The Bacula Director (bacula-dir) is the core management/scheduling service. It talks to a database (strongly recommend you choose PostgreSQL) to store all of the useful metadata, logs, etc. This database is referred to as the Catalog. You generally work with the director through the bconsole command, which can be run locally or remotely. Yes, it’s CLI-based. It feels dusty, but it’s incredibly reliable.
The Catalog keeps track of the Job history, configured Volumes, the files that were backed up and where they are stored, etc.
It’s important to remember that most Bacula operations are non-destructive. If you choose to Delete or Purge a Volume, this is a logical change within Bacula’s Catalog. The actual storage Volume is not wiped or deleted in any way, it just removes information about it from the Catalog. Recycling it will let you mark it for reuse again, Deleting it ensures it will never use it again. But your data is still there. Truncating a Volume would wipe the data, so keep that in mind.
This also means if you choose to alter the retention period for a Pool, none of the Volumes get updated to those new parameters. If a new Volume is created in the Pool it will get the new settings, but the existing ones don’t. You have to manually update it in the Catalog with a SQL query or issue a bconsole update volume= command for each Volume. Keep this in mind.
Fun fact, you don’t even need the Catalog to restore a backup. If you lost your Catalog but you have the Volumes it’s still possible to get the data off it. I’ve never had to do this procedure before, but it can be done: the bscan command can rebuild the Catalog, bextract can directly extract the Volume, and bls can list the Volume contents.
It’s important to remember that Bacula does NOT want to ever delete your data, only overwrite/reuse a Volume after it has passed its retention period and/or was Purged (automatically or manually).
And if you really want to, you can schedule Restore Jobs that automatically restore files on a schedule to help you test/validate your backups...
Storage Daemon
The Storage Daemon (bacula-sd) is the service you run wherever you want data to be stored. You can operate as many Storage Daemons as you want. It is possible to have your backups on a regular disk/filesystem, S3, a tape library, etc. You can even design a type of tiered-storage system, make custom Jobs that execute to copy or move an entire backup to a new place, etc.
Bacula Starter Kit
Here is an example of a configuration you can build off of. We’re going to be running the Director and Storage Daemon on the same server, but that server will also run its own File Daemon so it can backup itself (the Catalog, mostly). The configuration order doesn’t really matter. You can refer to something like a FileSet before it’s actually defined.
We’ll start with the Storage Daemon so we have somewhere to put the backups.
Keep in mind that connecting to the bconsole and running “reload” will live update the config without having to restart the Director.
# /usr/local/etc/bacula-sd.conf
Storage {
Name = bacula.localdomain
SDPort = 9103
WorkingDirectory = "/var/db/bacula"
Pid Directory = "/var/run"
Plugin Directory = "/usr/local/lib"
Maximum Concurrent Jobs = 20
}
# Define a Director that can talk to this Storage Daemon
# and which password it must use
Director {
Name = bacula.localdomain
Password = "random_password_for_sd"
}
# You could have many "devices" attached to
# a single Storage Daemon that can be used by
# different backups.
# This is a good default example for backups
# that go to a filesystem location instead of a tape library
Device {
Name = MyBackup
Media Type = File
Archive Device = /mnt
LabelMedia = yes;
Random Access = Yes;
AutomaticMount = yes;
RemovableMedia = no;
AlwaysOpen = no;
Maximum Concurrent Jobs = 5
}
# Ship logs to a Director
Messages {
Name = Standard
director = bacula.localdomain = all
}
Ok, now let’s configure the Director.
# /usr/local/etc/bacula-dir.conf
Director {
Name = bacula.localdomain
DIRport = 9101
QueryFile = "/usr/local/share/bacula/query.sql"
WorkingDirectory = "/var/db/bacula"
PidDirectory = "/var/run"
Maximum Concurrent Jobs = 20
Password = "some_random_pw" # the default bconsole password
Messages = Daemon
}
# The database config
# I recommend running a Postgres locally
# because backups won't work if Postgres
# is down, too many db connections, etc.
Catalog {
Name = MyCatalog
dbname = "bacula"
dbuser = "bacula"
dbpassword = "your_db_password"
DB Address = "localhost"
DB Port = 5432
}
# A default Job template that can be used as the
# basis for other jobs or when manually scheduling
# a new job from scratch
JobDefs {
Name = "DefaultJob"
Type = Backup
Level = Incremental
Client = bacula.localdomain
FileSet = "Full Set"
Schedule = "WeeklyCycle"
Storage = bacula.localdomain
Messages = Standard
Pool = Scratch
SpoolAttributes = yes
Priority = 10
Write Bootstrap = "/var/db/bacula/%c.bsr"
}
# Default Job to backup the Catalog
Job {
Name = "BackupCatalog"
JobDefs = "DefaultJob"
Level = Full
FileSet="Catalog"
Schedule = "WeeklyCycleAfterBackup"
Pool = Catalog
# This creates an ASCII copy of the catalog
# Arguments to make_catalog_backup.pl are:
# make_catalog_backup.pl <catalog-name>
RunBeforeJob = "/usr/local/share/bacula/make_catalog_backup.pl MyCatalog"
# This stock script deletes the copy of the catalog dump, but a simple rm would do
RunAfterJob = "/usr/local/share/bacula/delete_catalog_backup"
Write Bootstrap = "/var/db/bacula/%n.bsr"
Priority = 11 # Ensures it runs last if Job is scheduled concurrently
}
#
# Standard Restore template, to be changed by Console program
# Only one such job is needed for all Jobs/Clients/Storage ...
#
Job {
Name = "RestoreFiles"
Type = Restore
Client=bacula.localdomain
Storage = bacula.localdomain
# The FileSet and Pool directives are not used by Restore Jobs
# but must not be removed
FileSet="Full Set"
Pool = Scratch
Messages = Standard
# Ensures that if you restore to the wrong server at
# least you won't overwrite any files by default
# You can change this before scheduling a Job, though.
Where = /tmp/bacula-restores
}
# A default list of files/paths that can be
# used by any Job
FileSet {
Name = "Full Set"
Include {
Options {
signature = SHA256
}
File = /
}
Exclude {
File = /tmp
File = /proc
File = /tmp
File = /sys
File = /.journal
File = /.fsck
}
}
# A default resusable backup schedule
Schedule {
Name = "WeeklyCycle"
Run = Full 1st sun at 23:05
Run = Differential 2nd-5th sun at 23:05
Run = Incremental mon-sat at 23:05
}
# This schedule is used by the Catalog backup
Schedule {
Name = "WeeklyCycleAfterBackup"
Run = Full sun at 23:10
}
# This is the Catalog's FileSet
# the sql will get dumped before
# the Job executes
FileSet {
Name = "Catalog"
Include {
Options {
signature = SHA256
}
File = "/var/db/bacula/bacula.sql"
}
}
# The local client/bacula-fd for backing up
# the Catalog on this Director
Client {
Name = bacula.localdomain
Address = localhost
FDPort = 9102
Catalog = MyCatalog
Password = "some_random_pw"
File Retention = 60 days
Job Retention = 6 months
AutoPrune = yes
}
# Reasonable message delivery -- send most everything to email address
# and to the console
Messages {
Name = Standard
mailcommand = "/usr/local/sbin/bsmtp -h your-mta.localdomain -f \"\(Bacula\) \<bacula@bacula.localdomain\>\" -s \"Bacula: %l %t %e of %n\" %r"
operatorcommand = "/usr/local/sbin/bsmtp -h your-mta.localdomain -f \"\(Bacula\) \<bacula@bacula.localdomain\>\" -s \"Bacula: Intervention needed for %j\" %r"
#mail on error = you@your-email.com = all, !skipped # in case you only want error emails
mail = you@your-email.com = all, !skipped
operator = you@your-email.com = mount
console = all, !skipped, !saved
syslog = all, !skipped
catalog = all
}
# I also want emails for any other events
Messages {
Name = Daemon
mailcommand = "/usr/local/sbin/bsmtp -h your-mta.localdomain -f \"\(Bacula\) \<bacula@bacula.localdomain\>\" -s \"Bacula daemon message\" %r"
mail = you@your-email.com = all, !skipped
console = all, !skipped, !saved
append = "/var/log/bacula.log" = all, !skipped
}
# A default pool definition
# You probably shouldn't use this but
# having a default around is useful.
# Best practice would be to define a Pool for
# each Job so you have full control and backups
# don't get mixed on the same Volumes.
# Nothing in this config is actually using this Pool.
Pool {
Name = Default
Pool Type = Backup
Recycle = yes # Bacula can automatically recycle Volumes
AutoPrune = yes # Prune expired volumes
Volume Retention = 365 days # One year by default
Maximum Volume Bytes = 5G # Limit Volume size to something reasonable
# Maximum Volumes = 100 # Limit number of Volumes in Pool
Label Format = "Default-" # Volumes will get names like Default-0001
}
Pool {
Name = Catalog
Pool Type = Backup
Recycle = yes
AutoPrune = yes
Volume Retention = 14 days
Use Volume Once = yes
Action on Purge = Truncate
Maximum Volume Bytes = 5G
Label Format = "Catalog-"
}
# Scratch pool definition
Pool {
Name = Scratch
Pool Type = Backup
}
Storage {
Name = bacula.localdomain
Address = bacula.localdomain
SDPort = 9103
Password = "random_password_for_sd"
Device = MyBackup # name of the device you want to backup to on this Storage Daemon
Media Type = File
}
# Useful trick for dynamically importing configs in a directory in case
# you want to dynamically generate them from templates with another tool
@|"find /usr/local/etc/bacula/clients -name '*.conf' -type f -exec echo @{} \;"
The Director config is now ready to do backups at least for itself (the Catalog), but we didn’t configure the File Daemon locally yet so the Director can tell it to execute the Jobs.
# /usr/local/etc/bacula/bacula-fd.conf
# Director allowed to connect to this fd
Director {
Name = bacula.localdomain
Password = "some_random_pw"
}
FileDaemon {
Name = bacula.localdomain
FDport = 9102
WorkingDirectory = /var/db/bacula
Pid Directory = /var/run
Maximum Concurrent Jobs = 20
Plugin Directory = /usr/local/lib
}
# Send all messages except skipped files back to Director
Messages {
Name = Standard
director = bacula.localdomain = all, !skipped, !restored, !verified, !saved
}
Now those Catalog backups can execute. But I also want to backup my Mac Mini, so here is a config file we’ll also import on the Director using that trick:
# /usr/local/etc/bacula/clients/macmini.conf
Client {
Name = Marks-MacMini
Address = Marks-MacMini.localdomain
FDPort = 9102
Catalog = MyCatalog
Password = "my_macmini_fd_password"
File Retention = 60 days
Job Retention = 6 months
AutoPrune = yes
}
Schedule {
Name = "Marks-MacMini_files"
Run = Full mon at 23:00
Run = Incremental tue-sun at 23:00
}
FileSet {
Name = "Marks-MacMini_files"
Include {
Options {
signature = SHA256
honor nodump flag = yes
onefs = yes
fstype = apfs
hfsplussupport = yes
xattrsupport = yes
acl support = yes
}
File = /Users
}
}
Job {
Name = "Marks-MacMini_files"
JobDefs = "DefaultJob"
Pool = Marks-MacMini_files-Pool
Schedule = "Marks-MacMini_files"
Client = Marks-MacMini
FileSet = "Marks-MacMini_files"
Allow Duplicate Jobs = no
RunScript {
RunsWhen = Before
RunsOnClient = no
FailJobOnError = yes
# script for doing Wake on LAN
Command = /usr/local/etc/bacula/wake_macmini.sh
}
}
Pool {
Name = Marks-MacMini_files-Pool
Pool Type = Backup
Storage = bacula.localdomain
Recycle = yes
AutoPrune = yes
Maximum Volume Jobs = 1
Use Volume Once = yes
Label Format = "Marks-MacMini_files-"
Action on Purge = Truncate
Maximum Volume Bytes = 5G
Volume Retention = 14 days
}
Documentation
There is a derth of documentation available for Bacula, and the good news is that even if you encounter decades old docs or discussions they are probably still accurate. Check out the expansive HTML and PDF documentation available here.
Proving The Backups Work
Instant Restore From Backup
I made this little script so I can automatically restore a file with one command. It works because my clients are named to match their hostnames, and I have bconsole already configured to talk to the Director.
#!/bin/sh
# I call it "~/bin/brestorefile"
# FILENAME can be a file or a text file with a list of files
# if specified as </path/to/file
# but will be tricky to do with shell...
FILENAME=$1
echo "restore client=$(hostname) where=/ current select yes" | sudo bconsole
Restoring Interactively
Bacula has a nice feature where it can present you a virtual view of the filesystem from the most recent backup set and let you traverse it like a normal shell. You use a “mark” command choose the files or directories you want to restore. It works very well. Here’s an example:
> sudo bconsole
Connecting to Director bacula.i.feld.me:9101
1000 OK: 10002 bacula.i.feld.me Version: 15.0.3 (25 March 2025)
Enter a period to cancel a command.
*restore client=thinkpad-t480.i.feld.me where=/ current select yes
Using Catalog "MyCatalog"
Automatically selected FileSet: thinkpad-t480.i.feld.me_files
+-------+-------+-----------+----------------+---------------------+------------------------------------+
| jobid | level | jobfiles | jobbytes | starttime | volumename |
+-------+-------+-----------+----------------+---------------------+------------------------------------+
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0368 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0369 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0365 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0367 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0364 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0362 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0360 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0366 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0361 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0370 |
| 1,502 | F | 2,469,854 | 57,413,478,072 | 2025-11-03 23:50:25 | thinkpad-t480.i.feld.me_files-0363 |
| 1,519 | I | 161,883 | 5,779,869,217 | 2025-11-04 23:10:48 | thinkpad-t480.i.feld.me_files-0371 |
| 1,519 | I | 161,883 | 5,779,869,217 | 2025-11-04 23:10:48 | thinkpad-t480.i.feld.me_files-0372 |
| 1,523 | I | 1,347 | 527,428,742 | 2025-11-04 23:19:20 | thinkpad-t480.i.feld.me_files-0373 |
+-------+-------+-----------+----------------+---------------------+------------------------------------+
You have selected the following JobIds: 1502,1519,1523
Building directory tree for JobId(s) 1502,1519,1523 ...
++++++++++++++++++++++++++++++++++++++++++++
2,218,804 files inserted into the tree.
You are now entering file selection mode where you add (mark) and
remove (unmark) files to be restored. No files are initially added,
unless you used the "all" keyword on the command line.
Enter "done" to leave this mode.
cwd is: /
$ cd /usr/local/etc
cwd is: /usr/local/etc/
$ ls
# all the files were listed here, but I redacted them
$ mark sudo.conf
1 file marked.
$ done
Bootstrap records written to /var/db/bacula/bacula.i.feld.me.restore.1.bsr
The Job will require the following (*=>InChanger):
Volume(s) Storage(s) SD Device(s)
===========================================================================
thinkpad-t480.i.feld.me_files-0371 bacula.i.feld.me EZBackup
Volumes marked with "*" are in the Autochanger.
1 file selected to be restored.
Using Catalog "MyCatalog"
Job queued. JobId=1528
I received an email a second later confirming the file was restored successfully.
Final Notes
That seems like a lot, and I hope I didn’t mess anything up when transcribing the configs, but this should get you well on your way to a fast, reliable backup system. There are some web GUIs you can experiment with and I still have plans to build my own. The biggest issue is that the network protocol for talking to the the Director is not really documented except what’s in the C code, so everyone who builds an interface tends to use something like PHP/CGI that make bconsole executions and parse the text output. Yucky. We can do better, I’ve got some experiments laying around I need to dust off...