Hello everyone,
I finally managed to migrate my entire server to rootless podman quadlets.
Advantages in my opinion:
- No rootful docker containers
- Even regular systemd services like rclone in my example can be made dependent on a container, so they start after each other in the right order
- I have a separate config file for each container, so I can e.g. use one maria-DB container for different container apps without having to start them together in one stack, thanks to systemd quadlets, they start in the right order.
- Podman has auto-update inbuilt, no need for watchtower or similar addon aps (I have a small script in place that checks for updates once a week and notifies me by email, so I can check if some updates contain breaking changes that need manual interaction, afterwards I push the updates with āpodman auto-updateā.
Drawback:
- Withouth enabling the podman socket (which is not recommended), caddy docker proxy does not work, so I use a regular Caddyfile.
- There is no dedicated quadlet editor for the web like portainer, cockpit provides a podman plugin, but without proper quadlet support so far.
Since this was quite some work, I would like to share this with other forum members in case they are interested.
Quadlet files (.container &.network) go to ~/.config/containers/systemd, I keep .env files in the same folder.
.service files for rclone goes to ~/.config/systemd/user
Letās start with the seafile.env
TIME_ZONE=your timezone
SEAFILE_SERVER_HOSTNAME=https://my-seafiledomain.com
SEAFILE_SERVER_PROTOCOL=https
JWT_PRIVATE_KEY=your jwt keyENABLE_SEADOC=false
##I donāt use seadoc at allSEAFILE_MYSQL_DB_HOST=maria-db
SEAFILE_MYSQL_DB_PORT=3306
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=your seafilepasswordfor fresh install you also need to use the root db password variable, but not for migration from docker
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_dbCACHE_PROVIDER=redis
REDIS_HOST=seafile-redis
REDIS_PORT=6379
db.network (to create a custom podman network)
[Unit]
Description=Database Network
After=network-online.target[Network]
NetworkName=db
Subnet=10.90.0.0/24
Gateway=10.90.0.1[Install]
WantedBy=default.target
seafile-server.container
[Unit]
Description=Seafile Server
Requires=maria-db.service seafile-redis.service seafile-notifications.service oods.service
After=maria-db.service seafile-redis.service seafile-notifications.service oods.service[Container]
ContainerName=seafile-server
EnvironmentFile=seafile.env
Image=docker.io/seafileltd/seafile-mc:13.0-latest
Label=io.containers.autoupdate=registryNetwork=db
PublishPort=3001:80
Volume=/Media/Seafile:/shared[Service]
Restart=on-failure[Install]
WantedBy=default.target
seafile-notifications.container
[Unit]
Description=Seafile Notification Server
After=network.target
Wants=network.target[Container]
Image=docker.io/seafileltd/notification-server:13.0-latest
ContainerName=seafile-notifications
Label=io.containers.autoupdate=registryNetwork=db
EnvironmentFile=seafile.envVolume=/Media/Seafile/seafile/logs:/shared/seafile/logs
PublishPort=8083:8083
[Service]
Restart=on-failure[Install]
WantedBy=default.target
seafile-redis.container
[Unit]
Description=Seafile Redis Cache Server[Container]
ContainerName=seafile-redis
Image=docker.io/redis:latest
Label=io.containers.autoupdate=registry
Network=db
HealthCmd=redis-cli ping || exit 1[Service]
Restart=on-failure
Notify=healthy[Install]
WantedBy=default.target
maria-db.container
[Unit]
Description=MariaDB container
After=network.target[Container]
ContainerName=maria-db
Image=docker.io/mariadb:lts
Label=io.containers.autoupdate=registry
Network=dbHealthCmd=/usr/bin/mariadb-admin ping -h 127.0.0.1 -uroot -pyourrootpass --silent
HealthInterval=10s
HealthRetries=5
HealthTimeout=5sVolume=/Databases/MariaDB:/var/lib/mysql
Environment=MARIADB_ROOT_PASSWORD=yourrootpass
Environment=MYSQL_INITDB_SKIP_TZINFO=1
Environment=MYSQL_LOG_CONSOLE=true
Environment=MARIADB_AUTO_UPGRADE=1[Service]
Restart=on-failure
Notify=healthy[Install]
WantedBy=default.target
oods.container
[Unit]
Description=OnlyOffice Document Server[Container]
ContainerName=oods
Environment=JWT_ENABLED=true
Environment=JWT_SECRET=jwt secret
Image=docker.io/onlyoffice/documentserver:latest
Label=io.containers.autoupdate=registryPublishPort=8086:80
[Service]
Restart=on-failure[Install]
WantedBy=default.target
caddy.container
[Unit]
Description=Caddy container
After=network.target[Container]
ContainerName=caddy
Image=docker.io/caddy:latest
Label=io.containers.autoupdate=registry
Network=host
Environment=XDG_DATA_HOME=/dataVolume=/Storage/Caddy/Caddyfile:/etc/caddy/Caddyfile
Volume=/Storage/Caddy/data:/data/caddy
[Service]
Restart=on-failure[Install]
WantedBy=default.target
rclone.service (similar to seaf-fuse, but with full write access, not read only, optional)
[Unit]
Description=Rclone mount for Seafile
After=seafile-server.service
Requires=seafile-server.service[Service]
Type=simple
ExecStart=/usr/bin/rclone mount Seafile: /RClone
āvfs-cache-mode full
ādir-cache-time 72h
āpoll-interval 15s
ExecStop=/bin/fusermount -u /path/to/mountpoint
Restart=on-failure
RestartSec=5[Install]
WantedBy=default.target
Rclone needs to be configured once with an inbuilt assistance, this will create a config file within ~/.config and this service file will automatically start this (or you can comment out the line wantedby= and start it manuall).
Caddyfile
my-seafiledomain.com {
reverse_proxy :3001 {
}
handle_path /notification/* {
reverse_proxy :8083
}
handle_path /office/* {
reverse_proxy :8086 {
header_up X-Forwarded-Host {host}/office
}
}
}
Thanks a lot for daniel.pan in helping me to fix this caddy config for running onlyoffice in a subfolder of the seafile-domain (no additional port or separate sub-domain required).
To make this work, you also need to update your seahub_settings.py as followed:
ONLYOFFICE_APIJS_URL = āhttps://my-seafiledomain.com/office/web-apps/apps/api/documents/api.jsā
Final remarks, Iāve added the following lines to my .bashrc to facilitate podman container handling (restart, stop, status)
##Custom Podman functions for bash
alias sudr=āsystemctl --user daemon-reloadā
_pcac() {
local cur container_services
COMPREPLY=()
cur=ā${COMP_WORDS[COMP_CWORD]}ā# List all user services (active + inactive), match those derived from Quadlet (.container) container_services=$(systemctl --user list-units --type=service --all --no-legend \ | awk '{print $1}' \ | sed 's/\.service$//') COMPREPLY=( $(compgen -W "${container_services}" -- "${cur}") ) return 0}
complete -F _pcac pcr
complete -F _pcac pcs
complete -F _pcac pcstpcr() {
systemctl --user restart ā$1.serviceā
}
pcs() {
systemctl --user stop ā$1.serviceā
}
pcst() {
systemctl --user status ā$1.serviceā
}
##End of podman functions
So sudr is used to reload container quadlet config after changes were made to the files.
pcr seafile-server restarts the container (and all dependencies), pcs seafile-server stops the server and pcst seafile-server provides status information. For more detailed and realtime info on a container you can use e.g. podman logs -f seafile-server.
It is possible to harden this even further by using podman secrets for the passwords, but for now I guess this is enough. Feel free to comment on this setup or ask questions if you are interested in trying this.
Good luck!
Regards, Ruediger
P.S. Of note, I donāt use podman volumes for permanent data, but I prefer volume mounts directly to my zfs datasets to facilitate backups and direct access to all files from the host.