Radicale Server on Debian 12

  1. Introduction
  2. Requirements
    1. Tested with
  3. Radicale
    1. Setup
    2. Config
    3. Container
  4. Errors and gotcha yas
    1. .container location
  5. Radicale
  6. Android
    1. DAVx2
    2. Simple Calendar
  7. TLS
  8. Foot Notes
    1. iOS
  9. References

Introduction

In a video somewhere Tom said we should write things and post them to not reed
it. So here I wrote a thing and I’m posting it here. This took a while. Ask your
questions I’ll answer what I know. Radicale is a CalDAV server. We can use it to
sync contacts calendars and stuff between multiple devices. It’s really not
difficult to get working. I think the more interesting bit is the podman
container files and working with systemd. Debian 12 doesn’t support podmans new
.container files so we have to dig into how things work a little bit to get
things working the way a systemd user would expect when your target server host
is Debian 12. Good luck!
P.S. If it looks a little weird its because I wrote it in org mode then exported
to markdown. ORG MODE 4LYFE!

Requirements

systemd 183+
podman 4.4+ (We will cover lower versions though, because Debian 12)
podlet
openssl

Tested with

podlet 0.3.2-2
systemd 252 and 261.2
podman 6.0.1 and 4.3.1
OpenSSL 3.6.3 9 Jun 2026 (Library: OpenSSL 3.6.3 9 Jun 2026)

Radicale

Setup

First we need to create the directories we will use for our volumes. We need two
volumes. One that holds Radicales config files. The other one is for the actual
collection of ICS type files. For our examples we will just use the current directory.
$ mkdir config data
Ensure that your firewall is not blocking port 5232.

Config

We adjust the config by referencing the documentation found here,
https://github.com/Kozea/Radicale/blob/43eea27b7eb2aca10a616565325aaee8d561fb9c/config

# -*- mode: conf -*-
# vim:ft=cfg

# Config file for Radicale - A simple calendar server
#
# Place it into /etc/radicale/config (global)
# or ~/.config/radicale/config (user)

[server]
hosts = 0.0.0.0:5232, [::]:5232
max_connections = 5

# Base delay in case of error 5xx response (seconds)
delay_on_error = 3

# 100 Megabyte
max_content_length = 100000000
# 10 Megabyte (>= 3.5.10)
max_resource_size = 10000000
# 30 seconds
timeout = 30

[encoding]

# Encoding for responding requests
request = utf-8

# Encoding for storing local collections
stock = utf-8

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = sha512

# printf "your_username:$(openssl passwd -6 your_password)\n"

[storage]
# Folder for storing local collections, created if not present
filesystem_folder = /var/lib/radicale/collections

The Radicale application defines it’s users in an htpasswd file. You can specify
where that file lives inside the config file. For our purposes we will just put
it right into our config directory. In order to add a user we just need to
encrypt the password with openssl. Create a user by running this command,
$ printf "myuser:$(openssl passwd -6 somepassword)\n" >> config/users
When we run this command a user goes into the config/users file where the
password is created using SHA512. The user name is myuser and the password you
will need to type onto the web interface is somepassword.
The other important part is where the filesystem_folder points to. This is
where all your calendar data is stored. You should back this up somewhere
periodically.
Write this config out to the config/config.

Container

Using the documentation provided by Radicale I create this podman run command.

podman run \
       --name mycaldav \
       --userns=keep-id \
       --publish 5232:5232 \
       --volume ./config:/etc/radicale \
       --volume ./data:/var/lib/radicale \
       ghcr.io/kozea/radicale:stable

Running this in podman like this will enable us to auto-generate a kube
file. When you run this what should happen is the container will start to run
under the name mycaldav and your terminal will be attached to the containers
log.

Systemd & Podman 4.4+

Once your container is running we need to build a .container file. We can
generate this file by using podlet. Running the podlet command will dump a
.container file to stdout.
$ podlet generate container mycaldav > mycaldav.container
We will redirect it to a file. Once we have the .container file we need to
modify it a little bit so that it will create a workable .service file. In
order for a systemd unit file to be enabled so that it will start on boot.

[Unit]
Description=My caldav service
Wants=network-online.target
After=network-online.target

We want to tell systemd that we need networking to run our container. Add this
Unit section to the container file.

[Install]
WantedBy=default.target

We tell systemd we want this unit to start whenever the target is
default.target. We have to use this target because we are in the --user
space, see UNITS MANAGED BY THE USER SERVICE MANAGER in systemd.special. You
can add anything you would normally add to a unit file. Quadlet will just copy
and paste into the resulting unit file.

If your machine supports podman 4.4+ then we can take this .container file and
insert it into a path that systemd searches. You can see where these paths are
in podman-systemd.unit man page. For our example we will put it into
$XDG_CONFIG_HOME/containers/systemd/. When Systemd finds a .container file
it runs quadlet against these files to generate the .service files. The older
podman does not come with this and that’s why we need 4.4+.

If you want to run this container on a system that does not have quadlet you
will need to get the .service file.
$ /usr/lib/podman/quadlet -dryrun -user | tail -n +2 > mycaldav.service
This command will find all the .container files and print out an associated
.service file for it. We should only have one .container file on our system
so it’s safe to redirect it right to a file.
If you have more than one then each one is separated by a line that looks
something like ---mycaldav.service--- and you will need to copy it out.
Now we can use this .service anywhere there’s podman, even Debian 12! Well
almost.

Moving to Debian 12

When you move the .service file to the Debian 12 system you need to ensure to
put it where systemd looks. You can check the systemd.unit man page to see
where it’s looking for things. We will be running our service as a local user
and not as a system unit. You will want to put the service file in
$XDG_CONFIG_DIRS/systemd/user/. If this directory does not exist you can
simply mkdir it as you would anything else. Once you have the service file in
that directory we need to modify it slightly. Open the file in an editor and
delete the line under Unit that is labeled SourcePath. It seems like if this
is wrong then it trips up the daemon-reload message. It points back to where
your .container file was on the other machine but we don’t need it here. Once
we’ve done that we have to let systemd know it’s there. Run this command,
$ systemctl --user daemon-reload
This will force systemd to rescan everything to find new files and changes.
Once you’ve done this then we can actually start the service.
$ systemctl --user start mycaldav.service
We can check if it’s actually running with podman ps and you should see the
one container running under the name mycaldav. To check the logs of the
container we can check the journal.
$ journalctl --user -xefu mycaldav.service
From here you should be able to get to the web interface through
localhost:5232 or the IP address of the machine.

Have your pod start before login

If you want this command to always run on boot we can tell systemd that through
the use of the loginctl command. Run this command as root or with sudo,
# loginctl enable-linger $(id -u)
You can check the state of linger with,
$ loginctl list-users

  1. Fixing volumes

    In our example we use ./config and ./data. This probably isn’t what you want
    in your .service file. Once you have your .container file open it and edit
    the lines for the Volume to match what you actually the absolute path to the
    volumes on the host to be.

Errors and gotcha yas

.container location

If you didn’t put the .container file in the right place you’ll get an error
No files parsed from then it’ll show you a list of directories it tried to
search for.

Radicale

Open a web browser and navigate to the http:// then wherever your Radicale
container is running. You should arrive at a login screen. Go ahead and login to
the UI using the account we setup earlier, if you followed along precisely it
will be, myuser and somepassword. Once you’ve logged in you arrive at a
fairly blank page. Click the plus down on the bottom.

You will end up at this page that lets you create a new collection. We will keep
the defaults but set a title and description with a color and click
Create. You will end up back at the first page but now there’s a thing on
it. Radicale doesn’t let you look at the information inside this object. We will
need a client for that.

Android

DAVx2

For this setup I just have a standard Android device without Google on it. We
will need an app that will pull the caldav information down to our Android. For
this I’m using an app called DAVx2. It is in both the Play store as well as
fdroid. Once you install it and open it you’ll click on the plus sign to add
a new account. For our setup with Radicale we need to use the Login with URL and user name.

Here is what you should see. Go ahead click Continue.

This is the important part. We need to enter in our URL and our login info. You
have to put in the http or it does not work. Enter in http:// then wherever
your container is running. If you followed my steps earlier you just want to
enter myuser and somepassword for the login information.

The default here is fine.


Now you should see the calendar we created.
Click on the switch inside the calendar list item. Now your calendar will be
available to apps on your device.

Simple Calendar

When you open simple calendar you will see a gear in the top right. Click on
that and scroll down a little bit. You will see a section labeled CALDAV check
the box for CalDAV sync. When you click that you should see the calendars that
DAVx5 pulled in.

Check the calendar you want hit ok then back out to the main screen.

You should see the calendar name at the bottom. Now you can add events and
things and it’ll automatically sync back to your Radicale server.

TODO TLS

Yeah I don’t have a CA for my self hosting stuff yet, so idk.

Foot Notes

$XDG_CONFIG_HOME is usually ~/.config but you should check your distros
documentation.
Quadlet might be in another location like /usr/libexec/podman. You should
check the contents of your distros podman package to verify, dpkg -L podman.

iOS

In my research I have seen posts mentioning iOS doesn’t like CalDAV that are not
behind TLS. So if you only have iOS you might have to find a different client
for testing.

References

https://github.com/Kozea/Radicale
https://radicale.org/v3.html
man 5 podman-systemd.unit
man 5 systemd.unit
man 7 systemd.special
man 1 loginctl
https://www.davx5.com
https://simplemobiletools.com/simplecalculator/

1 Like