User:Bai-Chiang/Rootless podman with LinuxServer.io nextcloud and SWAG images

From ArchWiki

This is my notes of setting up rootless podman with LinuxServer.io images.

In this guide, I will set up three containers, Nextcloud, PostgreSQL and Secure Web Application Gateway (SWAG). The Nextcloud and PostgreSQL service will run as nextcloud user. SWAG container, handling SSL certificate and reverse proxy, will run as a separate user swag.

Installation

Install the podman and fuse-overlayfs packages.

If using linux-hardened kernel, create

/etc/sysctl.d/unprivileged_user_namespace.conf
kernel.unprivileged_userns_clone=1

to enable kernel.unprivileged_userns_clone then reboot machine to apply the change.

Configuration

Create podman users

# useradd -m swag
# useradd -m nextcloud

Enable lingering

# loginctl enable-linger swag
# loginctl enable-linger nextcloud

Set subuid and subgid

Note:
  • This section no longer needed. After shadow 4.11.1-3, new user will be added to /etc/subuid and /etc/subgid by default.
  • If you are using systemd-homed, the minimum UID and GID for containers must be at least 524288 (check the "begin container users" value in the output of userdbctl).

Add these lines to /etc/subuid and /etc/subgid.

/etc/subuid
swag:524288:65536
nextcloud:589824:65536

and

/etc/subgid
swag:524288:65536
nextcloud:589824:65536

This allocates uid and gid range 524288-589823 to swag user, and 589824-655359 to nextcloud user. If these ranges are already taken by other users, you need to shift/adjust the ranges accordingly.

Then run

$ podman system migrate

to propagate changes to subuid and subgid

Allow rootless podman access 443 port

Create file

/etc/sysctl.d/unprivileged_port_start.conf
net.ipv4.ip_unprivileged_port_start=443

Reboot system to apply change.

Firewall 443/tcp port

If using firewalld (default zone):

# firewall-cmd --permanent --add-port 443/tcp
# firewall-cmd --reload

If using uncomplicated Firewall:

# ufw allow 443/tcp

Test podman

Fist get an interactive shell as swag user, run

# machinectl shell swag@

Using machinectl instead of sudo -u swag so that it will start systemd user session.

Then run

$ podman run docker.io/library/hello-world

It should pull the docker.io/library/hello-world image, and print

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

To exit the interactive shell, run

$ exit

or press Ctrl+[ three times within 1s.

Reverse proxy

Set up reverse proxy using LinuxServer SWAG image. This tutorial will use mydomain.com and Cloudflare DNS records as an example.

Secure web application gateway (SWAG)

Get an interactive shell as swag user

# machinectl shell swag@

Create swag config directory

$ mkdir /home/swag/config

Run the container

$ podman run \
    --rm \
    --detach \
    --replace \
    --label io.containers.autoupdate=registry \
    --uidmap 1000:0:1 \
    --uidmap 0:1:1000 \
    --uidmap 1001:1001:64536 \
    --network=slirp4netns:allow_host_loopback=true,cidr=10.0.2.0/24,port_handler=slirp4netns \
    --dns=1.1.1.1 \
    --name=swag \
    --cap-add=NET_ADMIN \
    --env PUID=1000 \
    --env PGID=1000 \
    --env TZ=US/Eastern \
    --publish 443:443 \
    --volume /home/swag/config:/config:Z \
    --env URL=mydomain.com \
    --env VALIDATION=dns \
    --env SUBDOMAINS=www,nextcloud,dashboard \
    --env CERTPROVIDER=letsencrypt \
    --env DNSPLUGIN=cloudflare \
    --env EMAIL=mail@mydomain.com \
    --env ONLY_SUBDOMAINS=true \
    --env STAGING=false \
    --env PROPAGATION=60 \
    --env DOCKER_MODS='linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload' \
    lscr.io/linuxserver/swag:latest
  • --rm will automatically remove the container when it exits.
  • If another container with the same name already exists, --replace will replace and remove it.
  • podman-auto-update(1) looks up containers with --label io.containers.autoupdate=registry.
  • The LinuxServer.io containers use s6-overlay, with --env PUID=1000 and --env PGID=1000 it will run as UID=1000 inside the container. The three --uidmap flags will map the container UID=1000 to intermediate UID=0. For rootless podman, the intermediate UID=0 with be mapped to the UID for the user starting Podman. So the data in mapped volumes will be owned by swag user. See the manual for more detailed explanation of UID mapping.
  • allow_host_loopback=true will allow access the host loopback IP, default is 10.0.2.2, see podman-run(1). Since Nextcloud runs as a different user, you cannot create a bridged network. If the nextcloud container listen 4443 port on the host, the swag container could connect to the nextcloud container through host loopback address 10.0.2.2:4443. You do not need to create a firewall rule for 4443 port.
  • DOCKER_MODS= add dashboard and auto-reload mods. Check here for other available mods.

Check the log there will be an error message because we did not provide Cloudflare API token.

$ podman logs swag
...

ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.

DNS records

Create A record for www, nextcloud and dashboard for mydomain.com point to your server ip address. If using Cloudflare make sure it is DNS only not proxied/cached, the cloud logo should be grey not orange.

Create a Cloudflare API token https://dash.cloudflare.com/profile/api-tokens for mydomain.com with permission to edit DNS.

Edit

/home/swag/config/dns-conf/cloudflare.ini
# Replace with your values

# With global api key:
#dns_cloudflare_email = cloudflare@example.com
#dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

# With token (comment out both lines above and uncomment below):
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567

comment out dns_cloudflare_email and dns_cloudflare_api_key, then replace dns_cloudflare_api_token with your token. Change permission of this file

$ chmod 700 /home/swag/config/dns-conf/cloudflare.ini

Then restart the container

$ podman restart swag

Wait a while and check status it should say Successfully received certificate.

$ podman logs swag
...

Successfully received certificate.
...

Server ready

It is possible that the DNS record has not propagate yet, you can edit /etc/hosts file on your machine.

/etc/hosts
192.168.122.2  www.mydomain.com
192.168.122.2  nextcloud.mydomain.com

If the server ip is 192.168.122.2. Now connect to https://www.mydomain.com should find the SWAG welcome page.

Generate swag.service file

Podman support auto-update via systemd. Generate a systemd/user service file for swag:

$ mkdir -p /home/swag/.config/systemd/user/
$ cd /home/swag/.config/systemd/user/
$ podman generate systemd \
    --new \
    --name \
    --no-header \
    --restart-policy=on-failure \
    --container-prefix='' \
    --files \
    swag

This will create swag.service in the current working directory.

Then enable swag.service and podman-auto-update.timer

$ systemctl --user enable swag.service
$ systemctl --user enable podman-auto-update.timer

This should start swag container at boot and automatically update the container. Reboot the server and check the SWAG welcome page https://www.mydomain.com.

Set up reverse proxy for Nextcloud

Copy example config

$ cd /home/swag/config/nginx/proxy-confs/
$ cp nextcloud.subdomain.conf.sample nextcloud.subdomain.conf

Edit

/home/swag/config/nginx/proxy-confs/nextcloud.subdomain.conf
...
    set $upstream_app 10.0.2.2;
    set $upstream_port 4443;
...

The upstream_app is set to the host loopback IP. Since the Nextcloud instance will running under different user, not in the same bridge network, so access it through host. As an example, the Nextcloud service will listen on 4443 port.

Note: If you set up for an existing Nextcloud instance, follow the instruction on top of the nextcloud.subdomain.conf, or see this


Nextcloud

Get an interactive shell as nextcloud user

# machinectl shell nextcloud@

Create data directories

$ mkdir /home/nextcloud/data
$ mkdir /home/nextcloud/config
$ mkdir /home/nextcloud/database

Create nextcloud pod

$ podman pod create \
    --replace \
    --uidmap 1000:0:1 \
    --uidmap 0:1:1000 \
    --uidmap 1001:1001:64536 \
    --publish 127.0.0.1:4443:443 \
    --dns=1.1.1.1 \
    --name nextcloud-pod

Here the 4443 port match the port set in #Set up reverse proxy for Nextcloud. With 127.0.0.1 it only listen local connection.

Attach PostgreSQL container to the pod

$ podman run \
    --pod=nextcloud-pod \
    --rm \
    --detach \
    --replace \
    --label io.containers.autoupdate=registry \
    --name=postgres \
    --user 1000:1000 \
    --volume /home/nextcloud/database:/var/lib/postgresql/data:Z \
    --env POSTGRES_DB=nextcloud \
    --env POSTGRES_USER=nextcloud \
    --env POSTGRES_PASSWORD=nextcloud_database_password \
    docker.io/library/postgres:15-alpine

Attach nextcloud container to the pod

$ podman run \
    --pod=nextcloud-pod \
    --rm \
    --detach \
    --replace \
    --label io.containers.autoupdate=registry \
    --name=nextcloud \
    --env PUID=1000 \
    --env PGID=1000 \
    --env TZ=US/Eastern \
    --volume /home/nextcloud/config:/config:Z \
    --volume /home/nextcloud/data:/data:Z \
    lscr.io/linuxserver/nextcloud:latest

Now you can access Nextcloud with https://nextcloud.mydomain.com. It will show you the Nextcloud setup page. Configure the database: choose PostgreSQL, with database user/name/password specified in #Attach PostgreSQL container to the pod. The database host would be localhost.

Generate nextcloud systemd serivce files

To generate a systemd/user service file for Nextcloud:

$ mkdir -p /home/nextcloud/.config/systemd/user/
$ cd /home/nextcloud/.config/systemd/user/
$ podman generate systemd \
    --new \
    --name \
    --no-header \
    --restart-policy=on-failure \
    --container-prefix='' \
    --pod-prefix='' \
    --files \
    nextcloud-pod

Then enable nextcloud-pod.service, nextcloud.service, postgres.service and podman-auto-update.timer

$ systemctl --user enable nextcloud-pod.service
$ systemctl --user enable nextcloud.service
$ systemctl --user enable postgres.service
$ systemctl --user enable podman-auto-update.timer

Reboot the server and check if Nextcloud auto started.

Auto clean up

To automatically remove unused pods, containers, images, etc. Create podman-system-prune.service and podman-system-prune.timer in systemd user configs directory, /home/swag/.config/systemd/user/ and /home/nextcloud/.config/systemd/user/, with content

/home/username/.config/systemd/user/podman-system-prune.service
[Unit]
Description=Remove all unused pods, containers, images, networks, and volume data

[Service]
ExecStart=/usr/bin/podman system prune --all --force --filter "until=240h"

This will remove containers and images created 10 days (240h) ago.

/home/username/.config/systemd/user/podman-system-prune.timer
[Unit]
Description=podman image prune timer

[Timer]
OnCalendar=daily

[Install]
WantedBy=timers.target

Then enable the podman-system-prune.timer for both swag and nextcloud user, will run the cleanup everyday.