<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Archives des Operating systems - dbi Blog</title>
	<atom:link href="https://www.dbi-services.com/blog/category/operating-systems/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dbi-services.com/blog/category/operating-systems/</link>
	<description></description>
	<lastBuildDate>Fri, 26 Jun 2026 19:11:43 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/cropped-favicon_512x512px-min-32x32.png</url>
	<title>Archives des Operating systems - dbi Blog</title>
	<link>https://www.dbi-services.com/blog/category/operating-systems/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Highly Available, Load-Balanced PostgreSQL with Patroni, HAProxy, and Keepalived</title>
		<link>https://www.dbi-services.com/blog/highly-available-load-balanced-postgresql-with-patroni-haproxy-and-keepalived/</link>
					<comments>https://www.dbi-services.com/blog/highly-available-load-balanced-postgresql-with-patroni-haproxy-and-keepalived/#respond</comments>
		
		<dc:creator><![CDATA[Joan Frey]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 19:11:41 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[keepalived]]></category>
		<category><![CDATA[Load]]></category>
		<category><![CDATA[load balancer]]></category>
		<category><![CDATA[load balancing]]></category>
		<category><![CDATA[postgresql]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45342</guid>

					<description><![CDATA[<p>Patroni runs your PostgreSQL cluster and handles failover, promoting a replica the moment the primary dies and recording the change in its distributed store (etcd, Consul, or ZooKeeper). That part works on its own. Your applications still need one stable address to connect to, and they need writes to reach the primary while reads spread [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/highly-available-load-balanced-postgresql-with-patroni-haproxy-and-keepalived/">Highly Available, Load-Balanced PostgreSQL with Patroni, HAProxy, and Keepalived</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Patroni runs your PostgreSQL cluster and handles failover, promoting a replica the moment the primary dies and recording the change in its distributed store (etcd, Consul, or ZooKeeper). That part works on its own.</p>



<p class="wp-block-paragraph">Your applications still need one stable address to connect to, and they need writes to reach the primary while reads spread across replicas. HAProxy handles that routing, with a floating IP from Keepalived in front of it.</p>



<h1 id="h-how-the-tools-work-together" class="wp-block-heading">How the tools work together</h1>



<p class="wp-block-paragraph">Three components stands between your application and the database.</p>



<p class="wp-block-paragraph"><strong>Patroni</strong> manages replication and failover, and it runs an agent on every PostgreSQL node. Each agent exposes a small REST API (port 8008 by default) that reports that node&#8217;s role.</p>



<p class="wp-block-paragraph"><strong>HAProxy</strong> accepts client connections and forwards them to the right node. It asks Patroni&#8217;s REST API which node is the primary and which are replicas, then sends each connection to a matching node.</p>



<p class="wp-block-paragraph"><strong>Keepalived</strong> publishes a virtual IP that floats between your HAProxy hosts using VRRP. Your application connects to the VIP, so one HAProxy host going down doesn&#8217;t take the whole entry point with it.</p>



<p class="wp-block-paragraph">Your application talks to the VIP. Keepalived points the VIP at a live HAProxy. HAProxy forwards the connection to whichever PostgreSQL node Patroni reports as healthy for that role.</p>



<h1 id="h-the-health-check-method" class="wp-block-heading">The health-check method</h1>



<p class="wp-block-paragraph">HAProxy checks one port and routes to another.</p>



<p class="wp-block-paragraph">Patroni&#8217;s REST API returns an HTTP status that depends on the node&#8217;s role:</p>



<ul class="wp-block-list">
<li><code>GET /</code> returns <code>200</code> only on the leader (the primary). A non-leader node returns <code>503</code>.</li>



<li><code>GET /primary</code> is the explicit name for the same leader check.</li>



<li><code>GET /replica</code> returns <code>200</code> only on a running replica.</li>



<li><code>GET /read-only</code> returns <code>200</code> on the primary or a replica, any node that can serve a read.</li>
</ul>



<p class="wp-block-paragraph">In our case, HAProxy runs its health check against the API port (8008) and reads that status code, then forwards the SQL connection to the database port (5432). A node receives traffic only when its API answers <code>200</code> for the role that listener cares about. Point a listener&#8217;s check at <code>/</code> and it follows the primary. Point it at <code>/replica</code> and it follows the replicas. Patroni promotes a new leader, the status codes change, and HAProxy moves traffic to match within a couple of health-check cycles.</p>



<h1 id="h-a-first-and-simple-working-configuration" class="wp-block-heading">A first and simple working configuration</h1>



<p class="wp-block-paragraph">A two-node setup with <code>10.5.5.147</code> and <code>10.5.5.148</code> looks like this. One listener handles writes, the other handles reads.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
listen PG1
    bind *:5000
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_10.5.5.147_5432 10.5.5.147:5432 maxconn 100 check port 8008
    server postgresql_10.5.5.148_5432 10.5.5.148:5432 maxconn 100 check port 8008

listen PG1_ro
    bind *:5001
    option httpchk GET /replica
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_10.5.5.147_5432 10.5.5.147:5432 maxconn 100 check port 8008
    server postgresql_10.5.5.148_5432 10.5.5.148:5432 maxconn 100 check port 8008
</pre></div>


<p class="wp-block-paragraph">This example runs PostgreSQL on port 5432 and the Patroni API on 8008, so swap in whatever ports your deployment uses (the defaults are 5432 and 8008).</p>



<p class="wp-block-paragraph">Line by line:</p>



<ul class="wp-block-list">
<li><code>bind *:5000</code> and <code>bind *:5001</code> are the two addresses your applications connect to. Send writes to 5000 and reads to 5001.</li>



<li><code>option httpchk</code> (with no path) on the first listener checks Patroni&#8217;s root endpoint. Only the leader answers <code>200</code>, so HAProxy sends port 5000 traffic to the current primary.</li>



<li><code>option httpchk GET /replica</code> on the second listener checks the replica endpoint, so HAProxy sends port 5001 traffic to a replica.</li>



<li><code>http-check expect status 200</code> tells HAProxy that <code>200</code> means healthy and anything else means down.</li>



<li><code>inter 3s fall 3 rise 2</code> checks every 3 seconds, marks a server down after 3 failures, and brings it back after 2 successes.</li>



<li><code>on-marked-down shutdown-sessions</code> kills existing connections to a server the instant HAProxy marks it down, so clients reconnect and get rerouted instead of hanging on a dead node.</li>



<li><code>check port 8008</code> is the trick in action: health checks hit the Patroni API on 8008 while HAProxy forwards traffic to PostgreSQL on 5432.</li>



<li><code>maxconn 100</code> limit connections per server so you don&#8217;t exhaust PostgreSQL&#8217;s connection slots.</li>
</ul>



<p class="wp-block-paragraph">For a primary plus one or more replicas, this routes writes and reads to the right node and survives a failover.</p>



<h1 id="h-the-failure-mode-hiding-in-the-read-path" class="wp-block-heading">The failure mode hiding in the read path</h1>



<p class="wp-block-paragraph">Imagine a two-node cluster: one primary, one replica. The replica goes down. Maybe it crashed, maybe Patroni is mid-switchover and no standby exists for a few seconds.</p>



<p class="wp-block-paragraph">Your read traffic hits port 5001. That listener marks a server up only when <code>GET /replica</code> returns <code>200</code>, and right now no node is a replica. HAProxy has zero usable servers in the pool, so it refuses the connection. Read queries start failing.</p>



<p class="wp-block-paragraph">The primary is up the entire time, and it can serve those reads. Your config won&#8217;t send them there, because you told the read listener to look for replicas and nothing else. You&#8217;ve turned a degraded cluster that could still serve reads into a read outage. You feel this most on small clusters, and each failover passes through a window where the old primary becomes a replica and no standby is available yet. In the worst case, your replica is down, and one of your application is connecting to port 5001, resulting in errors.</p>



<h1 id="h-the-fix-fall-back-to-the-primary" class="wp-block-heading">The fix: fall back to the primary</h1>



<p class="wp-block-paragraph">Send reads to the primary when the read listener runs out of replicas, instead of dropping them.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
listen PG1
    bind *:5000
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_10.5.5.147_5432 10.5.5.147:5432 maxconn 100 check port 8008
    server postgresql_10.5.5.148_5432 10.5.5.148:5432 maxconn 100 check port 8008

listen PG1_ro
    bind *:5001
    option httpchk GET /replica
    http-check expect status 200
    use_backend PG1_ro_leader if { nbsrv(PG1_ro) eq 0 }
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_10.5.5.147_5432 10.5.5.147:5432 maxconn 100 check port 8008
    server postgresql_10.5.5.148_5432 10.5.5.148:5432 maxconn 100 check port 8008

backend PG1_ro_leader
    option httpchk GET /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_10.5.5.147_5432 10.5.5.147:5432 maxconn 100 check port 8008
    server postgresql_10.5.5.148_5432 10.5.5.148:5432 maxconn 100 check port 8008
</pre></div>


<p class="wp-block-paragraph">This new line carries the whole fix:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
use_backend PG1_ro_leader if { nbsrv(PG1_ro) eq 0 }
</pre></div>


<p class="wp-block-paragraph"><code>nbsrv(PG1_ro)</code> counts the usable servers in the <code>PG1_ro</code> pool, which here means the number of available replicas, since those servers pass the check only when <code>GET /replica</code> returns <code>200</code>. While at least one replica is up, the count stays above zero, the condition is false, and reads stay on the replicas. The moment the last replica drops, <code>nbsrv(PG1_ro)</code> hits zero, the condition fires, and HAProxy diverts reads to the <code>PG1_ro_leader</code> backend.</p>



<p class="wp-block-paragraph">That backend health-checks with <code>GET /primary</code>, so the only server it counts as up is the current primary. HAProxy sends reads to the primary until a replica returns, then shifts them back to the replica pool once a replica passes its check again.</p>



<p class="wp-block-paragraph">Three names have to agree for this to work. The backend you define (<code>backend PG1_ro_leader</code>), the backend you route to (<code>use_backend PG1_ro_leader</code>), and the pool you count (<code>nbsrv(PG1_ro)</code>) all reference the real section names. Drop in a stale name from an earlier version and HAProxy either refuses to start or counts the wrong pool.</p>



<h2 class="wp-block-heading">The /read-only shortcut and what it costs</h2>



<p class="wp-block-paragraph">Patroni offers <code>GET /read-only</code>, which returns <code>200</code> on the primary and the replicas alike. Point the read listener there and both the primary and the replicas serve reads, no fallback backend needed.</p>



<p class="wp-block-paragraph">The cost is read load on the primary even when your replicas are healthy and idle. The fallback approach keeps reads off the primary until the replicas are gone, then leans on it as a safety net. You protect the primary&#8217;s write capacity during normal operation and still keep reads alive during a replica outage.</p>



<p class="wp-block-paragraph">To keep lagging replicas out of the read pool, Patroni accepts a threshold on the replica check, for example <code>GET /replica?lag=10MB</code>, which fails any replica more than 10 MB behind. Pair that with the fallback and HAProxy drops the lagging replicas from rotation while reads still have somewhere to go.</p>



<h1 class="wp-block-heading">Keepalived: removing HAProxy as a single point of failure</h1>



<p class="wp-block-paragraph">One HAProxy host fronting the cluster moves the single point of failure up a layer. Run HAProxy on two hosts and let Keepalived float a virtual IP between them with VRRP. Your application connects to the VIP, and whichever HAProxy holds it answers.</p>



<p class="wp-block-paragraph">A minimal <code>keepalived.conf</code> on the primary HAProxy host:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
vrrp_script chk_haproxy {
    script &quot;killall -0 haproxy&quot;   # succeeds while the haproxy process is alive
    interval 2
    weight 2
}

vrrp_instance VI_1 {
    interface eth0
    state MASTER
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass changeme
    }
    virtual_ipaddress {
        10.0.0.1
    }
    track_script {
        chk_haproxy
    }
}
</pre></div>


<p class="wp-block-paragraph">The second HAProxy host runs the same file with <code>state BACKUP</code> and a lower <code>priority</code> (100). Both advertise over VRRP, and the higher priority holds the VIP. <code>chk_haproxy</code> runs every two seconds. If HAProxy dies on the active host, its priority drops and the backup takes the VIP, so an HAProxy crash on one host no longer takes the entry point down with it.</p>



<p class="wp-block-paragraph">Point your applications at <code>10.0.0.1:5000</code> for writes and <code>10.5.5.100:5001</code> for reads. Your applications never see which physical HAProxy does the work.</p>



<h1 class="wp-block-heading">Summary</h1>



<p class="wp-block-paragraph">Patroni keeps the cluster healthy and picks the leader. HAProxy turns Patroni&#8217;s REST API into routing, sending writes to the primary and reads to the replicas by health-checking the API port while forwarding to the database port. The naive read-only listener drops reads when the last replica goes down, even though the primary could serve them. Adding <code>use_backend ... if { nbsrv(...) eq 0 }</code> with a primary-checking backend closes that gap, and a lag threshold on the replica check keeps stale standbys out of rotation. Keepalived puts a floating VIP in front of two HAProxy instances so the proxy layer survives a host failure too.</p>



<p class="wp-block-paragraph">Writes reach the primary and reads spread across the replicas. Reads stay up as long as one node in the cluster is alive.</p>



<p class="wp-block-paragraph">Let me know if you find any improvements to this configuration <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>
<p>L’article <a href="https://www.dbi-services.com/blog/highly-available-load-balanced-postgresql-with-patroni-haproxy-and-keepalived/">Highly Available, Load-Balanced PostgreSQL with Patroni, HAProxy, and Keepalived</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/highly-available-load-balanced-postgresql-with-patroni-haproxy-and-keepalived/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Upgrade RHEL from 9.6 to 10.1 (when running PostgreSQL/Patroni)</title>
		<link>https://www.dbi-services.com/blog/upgrade-rhel-from-9-6-to-10-1-when-running-postgresql-patroni/</link>
					<comments>https://www.dbi-services.com/blog/upgrade-rhel-from-9-6-to-10-1-when-running-postgresql-patroni/#respond</comments>
		
		<dc:creator><![CDATA[Joan Frey]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 10:39:40 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[10]]></category>
		<category><![CDATA[leapp]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[operating system]]></category>
		<category><![CDATA[os]]></category>
		<category><![CDATA[RHEL]]></category>
		<category><![CDATA[upgrade]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43285</guid>

					<description><![CDATA[<p>Upgrading from RHEL 9.6 to 10.1 is not just a routine update, it’s a major platform shift. When your server runs PostgreSQL compiled from source and a Patroni-managed cluster, the complexity increases significantly. System libraries change, Python environments break, ICU versions evolve, and your database binaries may no longer start after reboot. In this guide, [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/upgrade-rhel-from-9-6-to-10-1-when-running-postgresql-patroni/">Upgrade RHEL from 9.6 to 10.1 (when running PostgreSQL/Patroni)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Upgrading from RHEL 9.6 to 10.1 is not just a routine update, it’s a major platform shift. When your server runs PostgreSQL compiled from source and a Patroni-managed cluster, the complexity increases significantly. System libraries change, Python environments break, ICU versions evolve, and your database binaries may no longer start after reboot.</p>



<p class="wp-block-paragraph">In this guide, I walk through a real-world in-place upgrade using Leapp, covering preparation, resolving high-severity warnings, executing the upgrade, recompiling PostgreSQL, fixing collation mismatches, and restoring Patroni.</p>



<h2 class="wp-block-heading" id="h-i-preparation">I. Preparation</h2>



<p class="wp-block-paragraph">Before the upgrade, you must ensure the current OS is healthy and fully patched.</p>



<h3 class="wp-block-heading" id="h-1-pause-high-availability">1. Pause High Availability</h3>



<p class="wp-block-paragraph">Prevent Patroni from triggering a failover during the reboot cycles. If you are using a single PostgreSQL cluster, stop it by stopping the service or by using pg_ctl stop.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
patronictl -c /etc/patroni/patroni.yml pause
systemctl stop patroni
</pre></div>


<h3 class="wp-block-heading" id="h-2-fix-subscription-amp-perform-full-update">2. Fix Subscription &amp; Perform Full Update</h3>



<p class="wp-block-paragraph">I&#8217;m using an old VM for this blog, and If just like me, you see 403 Forbidden errors on repositories like codeready-builder, refresh your registration:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;root@patroni2 ~]# sudo dnf update -y
Updating Subscription Management repositories.

This system is registered with an entitlement server, but is not receiving updates. You can use subscription-manager to assign subscriptions.

Red Hat CodeReady Linux Builder for RHEL 9 x86_64 (RPMs)                                                                                                     761  B/s | 480  B     00:00
Errors during downloading metadata for repository &#039;codeready-builder-for-rhel-9-x86_64-rpms&#039;:
  - Status code: 403 for https://cdn.redhat.com/content/dist/rhel9/9/x86_64/codeready-builder/os/repodata/repomd.xml (IP: 23.206.57.92)
Error: Failed to download metadata for repo &#039;codeready-builder-for-rhel-9-x86_64-rpms&#039;: Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried

&#x5B;root@patroni2 ~]# sudo subscription-manager clean
&#x5B;root@patroni2 ~]# sudo subscription-manager register --force
&#x5B;root@patroni2 ~]# sudo subscription-manager attach --auto
&#x5B;root@patroni2 ~]# sudo subscription-manager refresh

&#x5B;root@patroni2 ~]# sudo dnf update -y
...
Complete!

&#x5B;root@patroni2 ~]# reboot
</pre></div>


<h2 class="wp-block-heading" id="h-ii-the-leapp-upgrade-to-10-1">II. The Leapp Upgrade to 10.1</h2>



<h3 class="wp-block-heading" id="h-1-install-amp-analyze">1. Install &amp; Analyze</h3>



<p class="wp-block-paragraph">In this first phase, we are preparing the system for a major in-place upgrade using Leapp, the official upgrade framework for Red Hat–based distributions. When we install the package:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;root@patroni2 ~]# dnf install leapp-upgrade -y
...
Installed:
  leapp-0.20.0-1.el9.noarch                leapp-deps-0.20.0-1.el9.noarch          leapp-upgrade-el9toel10-0.23.0-1.el9.noarch       leapp-upgrade-el9toel10-deps-0.23.0-1.el9.noarch
  libdb-utils-5.3.28-57.el9_6.x86_64       python3-leapp-0.20.0-1.el9.noarch       systemd-container-252-55.el9_7.7.x86_64

Complete!
</pre></div>


<p class="wp-block-paragraph">When running leapp preupgrade &#8211;target 10.1, we are not performing the upgrade. Instead, Leapp performs a full system audit to determine if the server is ready for RHEL 10.1. It checks:</p>



<ul class="wp-block-list">
<li>Installed packages and their compatibility</li>



<li>Deprecated or removed libraries</li>



<li>Kernel drivers that will not exist in RHEL 10</li>



<li>Bootloader configuration (GRUB2)</li>



<li>GPG key validity</li>



<li>Custom system-level modifications (like dynamic linker changes)</li>



<li>&#8230;</li>
</ul>



<p class="wp-block-paragraph">Think of this step as a dry-run with intelligence.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;root@patroni2 ~]# sudo leapp preupgrade --target 10.1

...

============================================================
                      REPORT OVERVIEW
============================================================

HIGH and MEDIUM severity reports:
    1. GRUB2 core will be automatically updated during the upgrade
    2. Detected customized configuration for dynamic linker.
    3. Leapp detected loaded kernel drivers which are no longer maintained in RHEL 10.
    4. Failed to read GPG keys from provided key files
    5. Berkeley DB (libdb) has been detected on your system

Reports summary:
    Errors:                      0
    Inhibitors:                  0
    HIGH severity reports:       4
    MEDIUM severity reports:     1
    LOW severity reports:        1
    INFO severity reports:       3

Before continuing, review the full report below for details about discovered problems and possible remediation instructions:
    A report has been generated at /var/log/leapp/leapp-report.txt
    A report has been generated at /var/log/leapp/leapp-report.json
</pre></div>


<p class="wp-block-paragraph">After running the pre-upgrade analysis, the next step is to carefully review:</p>



<pre class="wp-block-preformatted">/var/log/leapp/leapp-report.txt</pre>



<p class="wp-block-paragraph">What we are looking for first is simple:</p>



<ul class="wp-block-list">
<li>Errors: 0</li>



<li>Inhibitors: 0</li>
</ul>



<p class="wp-block-paragraph">If an Inhibitor is present, the upgrade will be blocked entirely.<br>In my case, there were no blockers, but I did have several high severity warnings.</p>



<p class="wp-block-paragraph">High severity does not mean the upgrade will fail.<br>It means: This could break something, review it carefully.</p>



<p class="wp-block-paragraph">Let’s look at one concrete example from my system.</p>



<h3 class="wp-block-heading" id="h-2-high-severity-example-dynamic-linker-customization">2. High Severity Example – Dynamic Linker Customization</h3>



<p class="wp-block-paragraph">Leapp detected that my system had a custom dynamic linker configuration:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
Risk Factor: high

Title: Detected customized configuration for dynamic linker.

Summary: Custom configurations to the dynamic linker could potentially impact the upgrade in a negative way. The custom configuration includes modifications to /etc/ld.so.conf, custom or modified drop in config files in the /etc/ld.so.conf.d directory and additional entries in the LD_LIBRARY_PATH or LD_PRELOAD variables. These modifications configure the dynamic linker to use different libraries that might not be provided by Red Hat products or might not be present during the whole upgrade process. The following custom configurations were detected by leapp:

- The following drop in config files were marked as custom:

    - /etc/ld.so.conf.d/postgres.conf

Remediation: &#x5B;hint] Remove or revert the custom dynamic linker configurations and apply the changes using the ldconfig command. In case of possible active software collections we suggest disabling them persistently.

Key: cc9bd972af70b7a27f66a37b11a00dcfcb73b1bc

----------------------------------------
</pre></div>


<h4 class="wp-block-heading" id="h-what-does-this-actually-mean">What does this actually mean?</h4>



<p class="wp-block-paragraph">The dynamic linker (ld.so) is responsible for loading shared libraries at runtime.</p>



<p class="wp-block-paragraph">By modifying:</p>



<ul class="wp-block-list">
<li>/etc/ld.so.conf</li>



<li>files in /etc/ld.so.conf.d/</li>



<li>LD_LIBRARY_PATH</li>



<li><code>LD_PR</code>E<code>LOAD</code></li>
</ul>



<p class="wp-block-paragraph">we are telling the system to load non-standard or custom libraries. In PostgreSQL environments (especially with custom builds or extensions), this is common practice. However, during a major OS upgrade, these custom paths might:</p>



<ul class="wp-block-list">
<li>Point to libraries that do not exist in RHEL 10</li>



<li>Override new system libraries</li>



<li>Break dependency resolution mid-upgrade</li>
</ul>



<p class="wp-block-paragraph">Leapp flags this because it cannot guarantee consistency during the transition phase. In my case, it shouldn&#8217;t be an issue, because inside postgres.conf, I only have a path aiming to the lib directories of my PostgreSQL installation, which will not change, but we will still see how to prevent an error.</p>



<h4 class="wp-block-heading" id="h-understanding-the-remediation">Understanding the Remediation</h4>



<p class="wp-block-paragraph">The report clearly suggests:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Remove or revert the custom dynamic linker configurations and apply the changes using the ldconfig command.</p>
</blockquote>



<p class="wp-block-paragraph">In my case, the configuration was related to PostgreSQL, so temporarily removing it is safe for the upgrade preparation phase. Instead of deleting it permanently, I moved it aside:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;root@patroni2 ~]# sudo mv /etc/ld.so.conf.d/postgres.conf /tmp/postgres.conf.bak
&#x5B;root@patroni2 ~]# sudo ldconfig
</pre></div>


<p class="wp-block-paragraph">ldconfig rebuilds the system library cache now based only on standard paths.</p>



<h4 class="wp-block-heading" id="h-re-run-the-preupgrade-check">Re-Run the Preupgrade Check</h4>



<p class="wp-block-paragraph">After remediation, always re-run the preupgrade command and check the report again. If the fix was successful:</p>



<ul class="wp-block-list">
<li>The severity should disappear from the REPORT OVERVIEW</li>



<li>The issue should no longer appear in leapp-report.txt</li>
</ul>



<p class="wp-block-paragraph">This validation loop is important. We are progressively cleaning the system until it is fully compliant for upgrade.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
============================================================
                      REPORT OVERVIEW
============================================================

HIGH and MEDIUM severity reports:
    1. Leapp detected loaded kernel drivers which are no longer maintained in RHEL 10.
    2. GRUB2 core will be automatically updated during the upgrade
    3. Failed to read GPG keys from provided key files
    4. Berkeley DB (libdb) has been detected on your system

Reports summary:
    Errors:                      0
    Inhibitors:                  0
    HIGH severity reports:       3
    MEDIUM severity reports:     1
    LOW severity reports:        1
    INFO severity reports:       3

Before continuing, review the full report below for details about discovered problems and possible remediation instructions:
    A report has been generated at /var/log/leapp/leapp-report.txt
    A report has been generated at /var/log/leapp/leapp-report.json
</pre></div>


<p class="wp-block-paragraph">Only once the report is clean, or fully understood, should we proceed to the actual upgrade execution.</p>



<h3 class="wp-block-heading" id="h-2-execute-the-upgrade">2. Execute the upgrade</h3>



<p class="wp-block-paragraph">Once all errors and inhibitors are resolved, and high-severity findings have been reviewed or remediated, we are finally ready to perform the actual in-place upgrade. This is the moment where Leapp transitions from analysis mode to execution mode.</p>



<h4 class="wp-block-heading" id="h-about-the-repository-warning">About the Repository Warning</h4>



<p class="wp-block-paragraph">During the preupgrade phase, Leapp informed us that codeready-builder-&#8230; repositories are not officially supported during the upgrade process and are excluded by default.</p>



<p class="wp-block-paragraph">This is expected behavior as Leapp only enables a minimal, controlled set of repositories to ensure:</p>



<ul class="wp-block-list">
<li>Package consistency</li>



<li>Dependency resolution stability</li>



<li>Predictable upgrade paths</li>
</ul>



<p class="wp-block-paragraph">However, in PostgreSQL environments, some packages (extensions, development headers, libraries) may depend on CodeReady Builder. If a repository is truly required during the upgrade, we must explicitly enable it using:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
--enablerepo &lt;repoid&gt;
</pre></div>


<h4 class="wp-block-heading" id="h-running-the-upgrade">Running the Upgrade</h4>



<p class="wp-block-paragraph">Since I need CodeReady Builder for PostgreSQLdependencies, I ran:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
leapp upgrade --target 10.1 --enablerepo codeready-builder-for-rhel-10-x86_64-rpms
</pre></div>


<h4 class="wp-block-heading">What happens when we run this command?</h4>



<p class="wp-block-paragraph">At this stage, Leapp:</p>



<ul class="wp-block-list">
<li>Resolves and downloads required RHEL 10 packages</li>



<li>Builds a temporary upgrade environment</li>



<li>Prepares a special upgrade initramfs</li>



<li>Modifies the bootloader (GRUB) to boot into the upgrade environment on next reboot</li>
</ul>



<p class="wp-block-paragraph">The system is not upgraded immediately. The actual OS transition happens during the next boot.</p>



<p class="wp-block-paragraph">After the command completes, Leapp generates another report. Just like in the preupgrade phase, verify:</p>



<ul class="wp-block-list">
<li>Errors: 0</li>



<li>Inhibitors: 0</li>
</ul>



<p class="wp-block-paragraph">If everything looks clean, we can proceed.</p>



<h4 class="wp-block-heading" id="h-reboot-the-real-upgrade-begins">Reboot – The Real Upgrade Begins</h4>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
# reboot
</pre></div>


<p class="wp-block-paragraph">This is where the real upgrade starts. During boot:</p>



<ul class="wp-block-list">
<li>The system enters a temporary upgrade environment</li>



<li>Packages are replaced</li>



<li>Obsolete components are removed</li>



<li>Configuration files are migrated</li>



<li>The new RHEL 10 kernel is installed</li>
</ul>



<p class="wp-block-paragraph">This phase can take several minutes depending on your VM/server resources. My VM doesn&#8217;t have many resources and it took me around 30 minutes. Be patient, interrupting this process can leave the system in an inconsistent state.</p>



<h4 class="wp-block-heading" id="h-verifying-the-upgrade">Verifying the Upgrade</h4>



<p class="wp-block-paragraph">Once the server is back online, confirm the OS version:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;root@patroni2 ~]# cat /etc/os-release
NAME=&quot;Red Hat Enterprise Linux&quot;
VERSION=&quot;10.1 (Coughlan)&quot;
ID=&quot;rhel&quot;
ID_LIKE=&quot;centos fedora&quot;
VERSION_ID=&quot;10.1&quot;
PLATFORM_ID=&quot;platform:el10&quot;
PRETTY_NAME=&quot;Red Hat Enterprise Linux 10.1 (Coughlan)&quot;
ANSI_COLOR=&quot;0;31&quot;
LOGO=&quot;fedora-logo-icon&quot;
CPE_NAME=&quot;cpe:/o:redhat:enterprise_linux:10.1&quot;
HOME_URL=&quot;https://www.redhat.com/&quot;
VENDOR_NAME=&quot;Red Hat&quot;
VENDOR_URL=&quot;https://www.redhat.com/&quot;
DOCUMENTATION_URL=&quot;https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/10&quot;
BUG_REPORT_URL=&quot;https://issues.redhat.com/&quot;
</pre></div>


<p class="wp-block-paragraph">REDHAT_BUGZILLA_PRODUCT=&#8221;Red Hat Enterprise Linux 10&#8243;<br>REDHAT_BUGZILLA_PRODUCT_VERSION=10.1<br>REDHAT_SUPPORT_PRODUCT=&#8221;Red Hat Enterprise Linux&#8221;<br>REDHAT_SUPPORT_PRODUCT_VERSION=&#8221;10.1&#8243;</p>



<p class="wp-block-paragraph">This confirms that we are now running RHEL 10.1.</p>



<h2 class="wp-block-heading" id="h-iii-post-upgrade-database-recovery">III. Post-Upgrade Database Recovery</h2>



<p class="wp-block-paragraph">Once you login to RHEL 10.1, your Postgres binaries in /u01 will fail because libicuuc.so.67 (from RHEL 9) is missing. It can also fail because of other libraries.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
14:45:55 postgres@patroni2:/home/postgres/ &#x5B;test-op-patroni] pgstart
/u01/app/postgres/product/17/db_6/bin/postgres: error while loading shared libraries: libicuuc.so.67: cannot open shared object file: No such file or directory
no data was returned by command &quot;&quot;/u01/app/postgres/product/17/db_6/bin/postgres&quot; -V&quot;
command not found
program &quot;postgres&quot; is needed by pg_ctl but was not found in the same directory as &quot;/u01/app/postgres/product/17/db_6/bin/pg_ctl&quot;
</pre></div>


<h3 class="wp-block-heading" id="h-1-recompile-postgresql">1. Recompile PostgreSQL</h3>



<p class="wp-block-paragraph">Since you installed from source, you must re compile PostgreSQL with the new RHEL 10 system libraries. Here is the command I personally use to build it, with the postgres user:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
postgres@patroni2:/home/postgres/ &#x5B;dummy] MAJOR=&quot;17&quot;
postgres@patroni2:/home/postgres/ &#x5B;dummy] MINOR=&quot;6&quot;
postgres@patroni2:/home/postgres/ &#x5B;dummy] tar axf postgresql-${MAJOR}.${MINOR}.tar.gz
postgres@patroni2:/home/postgres/ &#x5B;dummy] mkdir build; cd $_
postgres@patroni2:/home/postgres/ &#x5B;dummy] export PGHOME=&quot;/u01/app/postgres/product/${MAJOR}/db_${MINOR}&quot;
postgres@patroni2:/home/postgres/ &#x5B;dummy] export SEGSIZE=2
postgres@patroni2:/home/postgres/ &#x5B;dummy] export BLOCKSIZE=8
postgres@patroni2:/home/postgres/ &#x5B;dummy] meson setup . ../postgresql-${MAJOR}.${MINOR}
postgres@patroni2:/home/postgres/ &#x5B;dummy] meson configure -Dprefix=${PGHOME}                   -Dbindir=${PGHOME}/bin                   -Ddatadir=${PGHOME}/share                   -Dincludedir=${PGHOME}/include                   -Dlibdir=${PGHOME}/lib                   -Dsysconfdir=${PGHOME}/etc                   -Dpgport=5432                   -Dplperl=enabled                   -Dplpython=enabled                   -Dssl=openssl                   -Dpam=enabled                   -Dldap=enabled                   -Dlibxml=enabled                   -Dlibxslt=enabled                   -Dsegsize=${SEGSIZE}                   -Dblocksize=${BLOCKSIZE}                   -Dllvm=enabled                   -Duuid=ossp                   -Dzstd=enabled                   -Dlz4=enabled                   -Dzstd=enabled                   -Dgssapi=enabled                   -Dsystemd=enabled                   -Dicu=enabled                   -Dsystem_tzdata=/usr/share/zoneinfo                   -Dextra_version=&quot; dbi services build&quot;
postgres@patroni2:/home/postgres/ &#x5B;dummy] ninja
postgres@patroni2:/home/postgres/ &#x5B;dummy] ninja install
</pre></div>


<h3 class="wp-block-heading" id="h-2-restore-library-paths">2. Restore Library Paths</h3>



<p class="wp-block-paragraph">[root@patroni2 ~]# mv /tmp/postgres.conf.bak /etc/ld.so.conf.d/postgres.conf<br>[root@patroni2 ~]# ldconfig</p>



<h3 class="wp-block-heading" id="h-3-start-amp-fix-collation-mismatch">3. Start &amp; Fix Collation Mismatch</h3>



<p class="wp-block-paragraph">Postgres will now start, but will warn you about Collation Version Mismatches (2.34 vs 2.39).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
15:09:55 postgres@patroni2:/home/postgres/build/ &#x5B;test-op-patroni] pgstart
waiting for server to start.... done
server started
15:10:28 postgres@patroni2:/home/postgres/build/ &#x5B;test-op-patroni] psql
WARNING:  database &quot;postgres&quot; has a collation version mismatch
DETAIL:  The database was created using collation version 2.34, but the operating system provides version 2.39.
HINT:  Rebuild all objects in this database that use the default collation and run ALTER DATABASE postgres REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
psql (17.6 dbi services build)
Type &quot;help&quot; for help.
</pre></div>


<p class="wp-block-paragraph">Inside Postgres, run for every database:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
ALTER DATABASE postgres REFRESH COLLATION VERSION;
ALTER DATABASE

-- Repeat for other DBs if applicable
REINDEX DATABASE postgres;
</pre></div>


<p class="wp-block-paragraph">Your PostgreSQL is now starting properly and your server has been upgraded.</p>



<h3 class="wp-block-heading" id="h-4-in-case-of-a-patroni-cluster">4. In case of a patroni cluster</h3>



<p class="wp-block-paragraph">Since the system Python version has changed after the OS upgrade, your old .local venv is invalid. You must recreate it. Here is how I install patroni using the postgres user:</p>



<p class="wp-block-paragraph">$ python3 -m venv .local<br>$ .local/bin/pip3 install &#8211;upgrade pip<br>$ .local/bin/pip3 install &#8211;upgrade setuptools<br>$ .local/bin/pip3 install wheel<br>$ .local/bin/pip3 install psycopg[binary]<br>$ .local/bin/pip3 install python-etcd<br>$ .local/bin/pip3 install patroni<br>$ .local/bin/patroni version</p>



<p class="wp-block-paragraph">Check if the cluster sees the member again. If patronictl list is empty, a restart of the service is usually required to re-register with etcd.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
10:45:40 postgres@patroni2:/home/postgres/ &#x5B;test-op-patroni] patronictl list
+ Cluster: test-op-patroni (7565882985963789761) -+-----+------------+-----+
| Member | Host | Role | State | TL | Receive LSN | Lag | Replay LSN | Lag |
+--------+------+------+-------+----+-------------+-----+------------+-----+
+--------+------+------+-------+----+-------------+-----+------------+-----+
10:45:48 postgres@patroni2:/home/postgres/ &#x5B;test-op-patroni] sudo systemctl restart patroni
10:45:55 postgres@patroni2:/home/postgres/ &#x5B;test-op-patroni] patronictl list
+ Cluster: test-op-patroni (7565882985963789761) --+---------+----+-------------+-----+------------+-----+
| Member                 | Host           | Role   | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+------------------------+----------------+--------+---------+----+-------------+-----+------------+-----+
| patroni-tst-op-geapg02 | 192.168.56.142 | Leader | running | 11 |             |     |            |     |
+------------------------+----------------+--------+---------+----+-------------+-----+------------+-----+

</pre></div>


<h2 class="wp-block-heading" id="h-iv-conclusion">IV. Conclusion</h2>



<p class="wp-block-paragraph">Upgrading from RHEL 9.6 to 10.1 is a big move. It’s not just a simple update; it’s a total shift in the system&#8217;s foundation. Between hardware driver changes and library updates, you really have to pay attention to the details to keep your database running.</p>



<p class="wp-block-paragraph">RHEL 10.1 is a great, modern platform, but you can&#8217;t just click &#8220;update&#8221; and hope for the best. By planning the upgrade, planning to rebuild your binaries, refreshing your database objects, you can make the jump without the drama. Take the pre-upgrade report seriously, it’s there for a reason!</p>



<p class="wp-block-paragraph">That said, for a production PostgreSQL cluster, especially one managed with Patroni and etcd, I would not recommend this in-place upgrade approach. Even if Leapp makes the process technically possible, you are still:</p>



<ul class="wp-block-list">
<li>Modifying the operating system in place</li>



<li>Replacing core libraries underneath a running database stack</li>



<li>Trusting automated dependency resolution during a major version jump</li>
</ul>



<p class="wp-block-paragraph">In production, risk reduction should always be the priority.</p>



<p class="wp-block-paragraph">Instead, I strongly recommend provisioning new VMs or physical servers, installing RHEL 10.1 from scratch, deploying PostgreSQL, Patroni, and etcd cleanly, rebuilding the cluster from best practices, and then migrating the data from the old environment to the new one using replication or another appropriate method.</p>



<p class="wp-block-paragraph">Sometimes the safest upgrade… is a new cluster.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/upgrade-rhel-from-9-6-to-10-1-when-running-postgresql-patroni/">Upgrade RHEL from 9.6 to 10.1 (when running PostgreSQL/Patroni)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/upgrade-rhel-from-9-6-to-10-1-when-running-postgresql-patroni/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; REST API with SQL Server 2025 (3/3)</title>
		<link>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-rest-api-with-sql-server-2025-3-3/</link>
					<comments>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-rest-api-with-sql-server-2025-3-3/#respond</comments>
		
		<dc:creator><![CDATA[Amine Haloui]]></dc:creator>
		<pubDate>Thu, 14 May 2026 21:39:18 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[proxmox]]></category>
		<category><![CDATA[ZFS]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44525</guid>

					<description><![CDATA[<p>The proposed architecture consists in adding a small internal REST API on the Proxmox server in order to expose a controlled ZFS snapshot operation. SQL Server 2025 can then call this API through sp_invoke_external_rest_endpoint, instead of running SSH commands directly or relying on an external tool. The role of the API is deliberately limited: it [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-rest-api-with-sql-server-2025-3-3/">SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; REST API with SQL Server 2025 (3/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">The proposed architecture consists in adding a small internal REST API on the Proxmox server in order to expose a controlled ZFS snapshot operation. SQL Server 2025 can then call this API through sp_invoke_external_rest_endpoint, instead of running SSH commands directly or relying on an external tool.</p>



<p class="wp-block-paragraph">The role of the API is deliberately limited: it receives a snapshot request, checks that the requested zvol is authorized, and then runs the zfs snapshot command on the Proxmox side. An allowlist is used to restrict the ZFS volumes that can be accessed. This prevents a REST call from being able to manipulate any dataset on the server.</p>



<p class="wp-block-paragraph">With this approach, we can reproduce a behavior close to what an enterprise storage array provides, but using Proxmox and ZFS. It is important to note that Proxmox does not natively provide the same level of integration as Pure Storage for SQL Server snapshots. Pure Storage provides dedicated mechanisms and integrations. In our case, we need to build a specific orchestration layer. The REST API therefore acts as an adapter between SQL Server, which drives the snapshot backup workflow, and ZFS, which actually performs the storage-level snapshot.</p>



<h2 class="wp-block-heading" id="h-architecture">Architecture</h2>



<p class="wp-block-paragraph">Here is a global overview of the architecture:</p>



<ul class="wp-block-list">
<li>SQL Server freezes the database I/Os</li>



<li>SQL Server 2025 calls the internal REST API</li>



<li>The REST API validates the request and checks the zvol allowlist</li>



<li>The API triggers the ZFS snapshot on Proxmox</li>



<li>The API returns the snapshot information to SQL Server</li>



<li>SQL Server creates the metadata-only backup</li>



<li>The database I/Os are released</li>
</ul>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="998" height="1024" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-998x1024.png" alt="" class="wp-image-44526" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-998x1024.png 998w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-292x300.png 292w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-768x788.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-1496x1536.png 1496w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-65-1995x2048.png 1995w" sizes="(max-width: 998px) 100vw, 998px" /></figure>



<h2 class="wp-block-heading">REST API implementation</h2>



<p class="wp-block-paragraph">Under Proxmox, we install the required packages:</p>



<pre class="wp-block-code"><code>apt update
apt install -y python3-venv sudo openssl</code></pre>



<p class="wp-block-paragraph">We create a dedicated user:</p>



<pre class="wp-block-code"><code>useradd --system \
&nbsp; --home /opt/sql-zfs-api \
&nbsp; --shell /usr/sbin/nologin \
&nbsp; sqlsnap</code></pre>



<p class="wp-block-paragraph">We create the following folders:</p>



<pre class="wp-block-code"><code>mkdir -p /opt/sql-zfs-api
mkdir -p /etc/sql-zfs-api</code></pre>



<p class="wp-block-paragraph">We declare the authorized zvol :</p>



<pre class="wp-block-code"><code>cat &gt;/etc/sql-zfs-api/allowed-zvols &lt;&lt;'EOF'
sqlpool/pve/vm-302-disk-0
EOF</code></pre>



<p class="wp-block-paragraph">We create a root-only allowlist:</p>



<pre class="wp-block-code"><code>chown root:root /etc/sql-zfs-api/allowed-zvols
chmod 600 /etc/sql-zfs-api/allowed-zvols</code></pre>



<p class="wp-block-paragraph">Then we create the secured ZFS helper. This script is executed as root through sudo, but it rejects any dataset that is not defined in the allowlist.</p>



<pre class="wp-block-code"><code>cat &gt;/usr/local/sbin/sql-zfs-helper &lt;&lt;'EOF'
#!/usr/bin/env bash
set -euo pipefail

ALLOW_FILE="/etc/sql-zfs-api/allowed-zvols"
LOCK_FILE="/run/sql-zfs-helper.lock"

die() {
  echo "$*" &gt;&amp;2
  exit 1
}

exec 9&gt;"$LOCK_FILE"
flock -n 9 || die "another snapshot operation is already running"

&#091;&#091; -r "$ALLOW_FILE" ]] || die "allowlist not readable: $ALLOW_FILE"

mapfile -t ALLOWED_DATASETS &lt; &lt;(grep -Ev '^\s*(#|$)' "$ALLOW_FILE")

is_allowed() {
  local ds="$1"
  local allowed
  for allowed in "${ALLOWED_DATASETS&#091;@]}"; do
    &#091;&#091; "$ds" == "$allowed" ]] &amp;&amp; return 0
  done
  return 1
}

valid_snapname() {
  &#091;&#091; "$1" =~ ^&#091;A-Za-z0-9_.:-]{1,120}$ ]]
}

ACTION="${1:-}"
shift || true

case "$ACTION" in
  snapshot)
    SNAPNAME="${1:-}"
    shift || true

    valid_snapname "$SNAPNAME" || die "invalid snapshot name: $SNAPNAME"
    &#091;&#091; "$#" -ge 1 ]] || die "no zvol specified"
    &#091;&#091; "$#" -le 8 ]] || die "too many zvols"

    SNAPSHOTS=()

    for DS in "$@"; do
      is_allowed "$DS" || die "dataset not allowed: $DS"
      /sbin/zfs list -H -t volume -o name "$DS" &gt;/dev/null 2&gt;&amp;1 || die "zvol not found: $DS"

      FULLSNAP="${DS}@${SNAPNAME}"

      if /sbin/zfs list -H -t snapshot -o name "$FULLSNAP" &gt;/dev/null 2&gt;&amp;1; then
        die "snapshot already exists: $FULLSNAP"
      fi

      SNAPSHOTS+=("$FULLSNAP")
    done

    /sbin/zfs snapshot "${SNAPSHOTS&#091;@]}"
    /sbin/zfs hold sqlsnap "${SNAPSHOTS&#091;@]}"

    printf '{"status":"ok","snapshots":&#091;'
    SEP=""
    for S in "${SNAPSHOTS&#091;@]}"; do
      printf '%s"%s"' "$SEP" "$S"
      SEP=","
    done
    printf ']}\n'
    ;;

  list)
    /sbin/zfs list -H -t snapshot -o name -r sqlpool | grep '@sql_' || true
    ;;

  *)
    die "usage: sql-zfs-helper snapshot SNAPNAME ZVOL &#091;ZVOL...]"
    ;;
esac
EOF

chown root:root /usr/local/sbin/sql-zfs-helper
chmod 750 /usr/local/sbin/sql-zfs-helper
</code></pre>



<p class="wp-block-paragraph">We only allow the helper through sudo:</p>



<pre class="wp-block-code"><code>cat &gt;/etc/sudoers.d/sql-zfs-helper &lt;&lt;'EOF'
sqlsnap ALL=(root) NOPASSWD: /usr/local/sbin/sql-zfs-helper *
EOF

chmod 440 /etc/sudoers.d/sql-zfs-helper
visudo -cf /etc/sudoers.d/sql-zfs-helper</code></pre>



<p class="wp-block-paragraph">We install the FastAPI API:</p>



<pre class="wp-block-code"><code>python3 -m venv /opt/sql-zfs-api/venv
/opt/sql-zfs-api/venv/bin/pip install fastapi "uvicorn&#091;standard]"</code></pre>



<p class="wp-block-paragraph">We create the application file:</p>



<pre class="wp-block-code"><code>cat &gt;/opt/sql-zfs-api/app.py &lt;&lt;'EOF'
import os
import re
import json
import socket
import secrets
import subprocess
from datetime import datetime, timezone
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel, Field

API_KEY = os.environ.get("SQL_ZFS_API_KEY", "")
ALLOW_FILE = "/etc/sql-zfs-api/allowed-zvols"
SNAP_RE = re.compile(r"^&#091;A-Za-z0-9_.:-]{1,120}$")

app = FastAPI(title="SQL ZFS Snapshot API", version="1.0.0")


class SnapshotRequest(BaseModel):
    database: str = Field(..., min_length=1, max_length=128)
    vmid: int = 302
    snapname: str = Field(..., min_length=1, max_length=120)
    zvols: list&#091;str] = Field(..., min_length=1, max_length=8)


def load_allowed_zvols() -&gt; set&#091;str]:
    with open(ALLOW_FILE, "r", encoding="utf-8") as f:
        return {
            line.strip()
            for line in f
            if line.strip() and not line.strip().startswith("#")
        }


def check_api_key(x_sqlsnap_key: str | None) -&gt; None:
    if not API_KEY:
        raise HTTPException(status_code=500, detail="API key not configured")

    if not x_sqlsnap_key:
        raise HTTPException(status_code=401, detail="missing API key")

    if not secrets.compare_digest(x_sqlsnap_key, API_KEY):
        raise HTTPException(status_code=403, detail="invalid API key")


@app.get("/health")
def health():
    return {
        "status": "ok",
        "host": socket.gethostname(),
        "utc": datetime.now(timezone.utc).isoformat(),
    }


@app.post("/v1/sql-zfs/snapshot")
def create_snapshot(
    req: SnapshotRequest,
    x_sqlsnap_key: str | None = Header(default=None, alias="x-sqlsnap-key"),
):
    check_api_key(x_sqlsnap_key)

    if not SNAP_RE.fullmatch(req.snapname):
        raise HTTPException(status_code=400, detail="invalid snapname")

    allowed = load_allowed_zvols()

    for zvol in req.zvols:
        if zvol not in allowed:
            raise HTTPException(status_code=403, detail=f"zvol not allowed: {zvol}")

    cmd = &#091;
        "sudo",
        "/usr/local/sbin/sql-zfs-helper",
        "snapshot",
        req.snapname,
        *req.zvols,
    ]

    try:
        completed = subprocess.run(
            cmd,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=30,
            check=False,
        )
    except subprocess.TimeoutExpired:
        raise HTTPException(status_code=504, detail="zfs snapshot timeout")

    if completed.returncode != 0:
        raise HTTPException(
            status_code=500,
            detail={
                "error": completed.stderr.strip(),
                "stdout": completed.stdout.strip(),
            },
        )

    snapshots = &#091;f"{zvol}@{req.snapname}" for zvol in req.zvols]

    return {
        "status": "ok",
        "database": req.database,
        "vmid": req.vmid,
        "snapname": req.snapname,
        "snapshots": snapshots,
        "media_description": "zfs|" + socket.gethostname() + "|" + ";".join(snapshots),
    }
EOF

chown -R root:root /opt/sql-zfs-api
chmod 755 /opt/sql-zfs-api
chmod 644 /opt/sql-zfs-api/app.py
</code></pre>



<p class="wp-block-paragraph">We configure and generate the key:</p>



<pre class="wp-block-code"><code>APIKEY="$(openssl rand -hex 32)"
echo "$APIKEY"</code></pre>



<p class="wp-block-paragraph">We create the environment file:</p>



<pre class="wp-block-code"><code>cat &gt;/etc/sql-zfs-api/sql-zfs-api.env &lt;&lt;EOF
SQL_ZFS_API_KEY=$APIKEY
EOF

chown root:root /etc/sql-zfs-api/sql-zfs-api.env
chmod 600 /etc/sql-zfs-api/sql-zfs-api.env</code></pre>



<p class="wp-block-paragraph">We need to save the generated key.</p>



<p class="wp-block-paragraph">Next, we enable HTTPS. SQL Server sp_invoke_external_rest_endpoint calls HTTPS endpoints, and the documentation specifies that only HTTPS endpoints with TLS are supported.</p>



<pre class="wp-block-code"><code>openssl req -x509 -newkey rsa:4096 -sha256 -days 360 -nodes \
  -keyout /etc/sql-zfs-api/tls.key \
  -out /etc/sql-zfs-api/tls.crt \
  -subj "/CN=promox1" \
  -addext "subjectAltName=DNS:promox1,IP:192.168.1.110"

chown root:sqlsnap /etc/sql-zfs-api/tls.key /etc/sql-zfs-api/tls.crt
chmod 640 /etc/sql-zfs-api/tls.key
chmod 644 /etc/sql-zfs-api/tls.crt</code></pre>



<p class="wp-block-paragraph">The /etc/sql-zfs-api/tls.crt certificate must be imported into the Windows trusted root certification authorities on the SQL Server side. Otherwise, the HTTPS call may fail.</p>



<p class="wp-block-paragraph">We create the systemd service:</p>



<pre class="wp-block-code"><code>cat &gt;/etc/systemd/system/sql-zfs-api.service &lt;&lt;'EOF'
&#091;Unit]
Description=SQL Server to ZFS Snapshot API
After=network-online.target
Wants=network-online.target

&#091;Service]
User=sqlsnap
Group=sqlsnap
WorkingDirectory=/opt/sql-zfs-api
EnvironmentFile=/etc/sql-zfs-api/sql-zfs-api.env
ExecStart=/opt/sql-zfs-api/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8443 --ssl-keyfile /etc/sql-zfs-api/tls.key --ssl-certfile /etc/sql-zfs-api/tls.crt
Restart=on-failure
RestartSec=3

&#091;Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now sql-zfs-api
systemctl status sql-zfs-api
</code></pre>



<p class="wp-block-paragraph">We check the status of our API:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="697" height="186" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-67.png" alt="" class="wp-image-44528" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-67.png 697w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-67-300x80.png 300w" sizes="(max-width: 697px) 100vw, 697px" /></figure>



<p class="wp-block-paragraph">It is possible to call the API in PowerShell using Invoke-RestMethod with PowerShell 7:</p>



<pre class="wp-block-code"><code>$headers = @{
"Content-Type"  = "application/json"
"x-sqlsnap-key" = "MyKey"
}

$body = @{
database = "StackOverflow"
vmid     = 302
snapname = "StackOverflow_test010"
zvols    = @("sqlpool/pve/vm-302-disk-0")
} | ConvertTo-Json -Depth 5

Invoke-RestMethod `
-Uri "https://192.168.1.110:8443/v1/sql-zfs/snapshot" `
-Method Post `
-Headers $headers `
-Body $body `
-ContentType "application/json" `
-SkipCertificateCheck
</code></pre>



<p class="wp-block-paragraph">This gives:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="833" height="510" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-80.png" alt="" class="wp-image-44590" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-80.png 833w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-80-300x184.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-80-768x470.png 768w" sizes="(max-width: 833px) 100vw, 833px" /></figure>



<h2 class="wp-block-heading" id="h-test-from-sql-server">Test from SQL Server</h2>



<p class="wp-block-paragraph">A certificate was generated on Proxmox and it needs to be imported on the SQL Server host. In my case, it was located here:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="404" height="79" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-69.png" alt="" class="wp-image-44530" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-69.png 404w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-69-300x59.png 300w" sizes="auto, (max-width: 404px) 100vw, 404px" /></figure>



<p class="wp-block-paragraph">I then imported it on Windows Server:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="788" height="149" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-70.png" alt="" class="wp-image-44531" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-70.png 788w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-70-300x57.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-70-768x145.png 768w" sizes="auto, (max-width: 788px) 100vw, 788px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="118" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-71-1024x118.png" alt="" class="wp-image-44532" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-71-1024x118.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-71-300x34.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-71-768x88.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-71.png 1384w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">For testing purposes, I created something simple. On the SQL Server side, we can create a database that will be used to store our future stored procedure. This procedure will allow us to interact with the API. In my case, I created a database called dbi_tools:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="244" height="131" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-72.png" alt="" class="wp-image-44533" /></figure>



<p class="wp-block-paragraph">This database will contain a credential. In our case, the DATABASE SCOPED CREDENTIAL is used to securely store the authentication information required to call the REST API from SQL Server. This allows us, for example, to protect the API key:</p>



<pre class="wp-block-code"><code>USE &#091;dbi_tools]
GO

IF NOT EXISTS (
    SELECT 1
    FROM sys.symmetric_keys
    WHERE name = '##MS_DatabaseMasterKey##'
)
BEGIN
    CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'MyStrongPassword_%99';
END
GO

CREATE DATABASE SCOPED CREDENTIAL &#091;https://192.168.1.110:8443/v1/sql-zfs/snapshot]
WITH
    IDENTITY = 'HTTPEndpointHeaders',
    SECRET = '{"x-sqlsnap-key":"MyAPIKey"}';
GO</code></pre>



<p class="wp-block-paragraph">We then create a stored procedure to encapsulate the code used to call the API:</p>



<pre class="wp-block-code"><code>USE dbi_tools;
GO

CREATE OR ALTER PROCEDURE dbo.usp_BackupDatabase_WithZfsSnapshot
    @DatabaseName sysname,
    @BackupDirectory nvarchar(4000) = N'D:\Backups\'
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @Url nvarchar(4000) =
        N'https://192.168.1.110:8443/v1/sql-zfs/snapshot';

    DECLARE @Vmid int = 302;

    DECLARE @ZvolsJson nvarchar(max) =
        N'&#091;"sqlpool/pve/vm-302-disk-0"]';

    DECLARE @Stamp varchar(20) =
        REPLACE(REPLACE(CONVERT(varchar(19), SYSUTCDATETIME(), 126), '-', ''), ':', '') + 'Z';

    DECLARE @SafeDbName nvarchar(128) =
        REPLACE(REPLACE(REPLACE(@DatabaseName, N' ', N'_'), N'&#091;', N''), N']', N'');

    DECLARE @SnapName nvarchar(128) =
        CONCAT(N'sql_', @SafeDbName, N'_', @Stamp);

    DECLARE @BackupFile nvarchar(4000) =
        CONCAT(@BackupDirectory, N'\', @SafeDbName, N'_', @Stamp, N'.bkm');

    DECLARE @Payload nvarchar(max) =
    (
        SELECT
            @DatabaseName AS &#091;database],
            @Vmid AS &#091;vmid],
            @SnapName AS &#091;snapname],
            JSON_QUERY(@ZvolsJson) AS &#091;zvols]
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    );

    DECLARE @ReturnCode int;
    DECLARE @Response nvarchar(max);
    DECLARE @SnapshotList nvarchar(max);

    SELECT @SnapshotList =
        STRING_AGG(CONCAT(&#091;value], N'@', @SnapName), N';')
    FROM OPENJSON(@ZvolsJson);

    DECLARE @MediaDescription nvarchar(max) =
        CONCAT(N'zfs|promox1|', @SnapshotList);

    DECLARE @Sql nvarchar(max);

    BEGIN TRY
        SET @Sql =
            N'ALTER DATABASE ' + QUOTENAME(@DatabaseName) +
            N' SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON;';

        EXEC sys.sp_executesql @Sql;

        EXEC @ReturnCode = sys.sp_invoke_external_rest_endpoint
            @url = @Url,
            @method = N'POST',
            @headers = N'{"Content-Type":"application/json","Accept":"application/json"}',
            @payload = @Payload,
            @credential = &#091;https://192.168.1.110:8443/v1/sql-zfs/snapshot],
            @timeout = 30,
            @response = @Response OUTPUT;

        IF @ReturnCode &lt;&gt; 0
        BEGIN
            DECLARE @Err nvarchar(max) =
                CONCAT(N'ZFS snapshot API failed. ReturnCode=', @ReturnCode, N' Response=', @Response);
            THROW 51001, @Err, 1;
        END;

        SET @Sql =
            N'BACKUP DATABASE ' + QUOTENAME(@DatabaseName) + N'
              TO DISK = @BackupFile
              WITH METADATA_ONLY,
                   FORMAT,
                   MEDIANAME = @MediaName,
                   MEDIADESCRIPTION = @MediaDescription,
                   NAME = @BackupName;';

        EXEC sys.sp_executesql
            @Sql,
            N'@BackupFile nvarchar(4000),
              @MediaName nvarchar(128),
              @MediaDescription nvarchar(max),
              @BackupName nvarchar(128)',
            @BackupFile = @BackupFile,
            @MediaName = @SnapName,
            @MediaDescription = @MediaDescription,
            @BackupName = @SnapName;

        SELECT
            @DatabaseName AS database_name,
            @SnapName AS zfs_snapshot_name,
            @SnapshotList AS zfs_snapshots,
            @BackupFile AS metadata_backup_file,
            @MediaDescription AS media_description,
            @Response AS api_response;
    END TRY
    BEGIN CATCH
        IF DATABASEPROPERTYEX(@DatabaseName, 'IsDatabaseSuspendedForSnapshotBackup') = 1
        BEGIN
            SET @Sql =
                N'ALTER DATABASE ' + QUOTENAME(@DatabaseName) +
                N' SET SUSPEND_FOR_SNAPSHOT_BACKUP = OFF;';

            EXEC sys.sp_executesql @Sql;
        END;

        THROW;
    END CATCH
END;
GO
</code></pre>



<p class="wp-block-paragraph">We then call the stored procedure:</p>



<pre class="wp-block-code"><code>EXEC dbi_tools.dbo.usp_BackupDatabase_WithZfsSnapshot
    @DatabaseName = N'StackOverflow',
    @BackupDirectory = N'D:\Backups\';</code></pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="137" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-73-1024x137.png" alt="" class="wp-image-44534" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-73-1024x137.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-73-300x40.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-73-768x102.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-73.png 1432w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The backup was generated :</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="630" height="149" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-74.png" alt="" class="wp-image-44535" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-74.png 630w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-74-300x71.png 300w" sizes="auto, (max-width: 630px) 100vw, 630px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="777" height="411" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-75.png" alt="" class="wp-image-44536" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-75.png 777w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-75-300x159.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-75-768x406.png 768w" sizes="auto, (max-width: 777px) 100vw, 777px" /></figure>



<h2 class="wp-block-heading" id="h-references">References</h2>



<p class="wp-block-paragraph"><a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-invoke-external-rest-endpoint-transact-sql?view=sql-server-ver17&amp;tabs=request-headers">sp_invoke_external_rest_endpoint</a></p>



<p class="wp-block-paragraph">Thank you. <a href="https://www.linkedin.com/in/amine-haloui-76968056/">Amine Haloui</a></p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-rest-api-with-sql-server-2025-3-3/">SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; REST API with SQL Server 2025 (3/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-rest-api-with-sql-server-2025-3-3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; Powershell implementation (2/3)</title>
		<link>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-2-3/</link>
					<comments>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-2-3/#respond</comments>
		
		<dc:creator><![CDATA[Amine Haloui]]></dc:creator>
		<pubDate>Thu, 14 May 2026 21:35:41 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[proxmox]]></category>
		<category><![CDATA[ZFS]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44497</guid>

					<description><![CDATA[<p>In the previous section, we discussed the drawbacks of running the commands manually. Indeed, the manual process was taking too much time and could directly impact the database state while the freeze was occurring. To address this issue, it is possible to automate the solution with PowerShell. The idea is to automate the different operations [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-2-3/">SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; Powershell implementation (2/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In the previous section, we discussed the drawbacks of running the commands manually. Indeed, the manual process was taking too much time and could directly impact the database state while the freeze was occurring.</p>



<p class="wp-block-paragraph">To address this issue, it is possible to automate the solution with PowerShell. The idea is to automate the different operations involved in the snapshot backup and restore process.</p>



<p class="wp-block-paragraph">We will use two scripts:</p>



<ul class="wp-block-list">
<li>One script to perform the backups and create the snapshots.</li>



<li>One script to perform the restores.</li>
</ul>



<h2 class="wp-block-heading" id="h-backup-process">Backup process</h2>



<p class="wp-block-paragraph">Here is how the backup process works:</p>



<ul class="wp-block-list">
<li>We connect to the corresponding SQL Server instance.</li>



<li>We change the state of the database using ALTER DATABASE &#8230; SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON. At this point, the I/Os are frozen.</li>



<li>We connect to the hypervisor through SSH.</li>



<li>We create the snapshot.</li>



<li>We back up the database using BACKUP DATABASE &#8230; WITH METADATA_ONLY.</li>



<li>We change the state of the database using ALTER DATABASE &#8230; SET SUSPEND_FOR_SNAPSHOT_BACKUP = OFF. At this point, the I/Os are unfrozen.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="627" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-1024x627.png" alt="" class="wp-image-44499" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-1024x627.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-300x184.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-768x470.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-1536x941.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-50-2048x1254.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Powershell implementation (backup)</h2>



<p class="wp-block-paragraph">Here is the code used to perform the backup:</p>



<pre class="wp-block-code"><code>param(
    &#091;string]$SqlInstance = "VM-WS25-SQL2",
    &#091;string]$Database    = "StackOverflow",
    &#091;string]$BackupDir   = "D:\Backups",
    &#091;string]$PveHost     = "192.168.1.110",
    &#091;string]$PveUser     = "MyUser",
    &#091;string&#091;]]$Zvols     = @("sqlpool/pve/vm-302-disk-0")
)

$Timestamp = Get-Date -Format "yyyyMMddTHHmmss"
$SnapName  = "sql_${Database}_${Timestamp}"

$DbSafe = $Database.Replace("]", "]]")
$BackupFile = Join-Path $BackupDir "${Database}_${Timestamp}.bkm"

$ZfsSnapshots = $Zvols | ForEach-Object { "$_@$SnapName" }
$ZfsSnapshotArgs = $ZfsSnapshots -join " "

$MediaDescription = "zfs|$PveHost|$ZfsSnapshotArgs"

$BackupFileSql = $BackupFile.Replace("'", "''")
$MediaSql = $MediaDescription.Replace("'", "''")

$connString = "Server=$SqlInstance;Database=master;Integrated Security=True;TrustServerCertificate=True;Application Name=ZFS-TSQL-Snapshot;"
$conn = New-Object System.Data.SqlClient.SqlConnection $connString

function Invoke-SqlNonQuery {
    param(&#091;string]$Sql)

    $cmd = $conn.CreateCommand()
    $cmd.CommandTimeout = 0
    $cmd.CommandText = $Sql
    &#091;void]$cmd.ExecuteNonQuery()
}

try {
    $conn.Open()

    Write-Host "Freezing SQL database writes..."
    Invoke-SqlNonQuery "ALTER DATABASE &#091;$DbSafe] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON;"

    Write-Host "Taking ZFS snapshot on Proxmox..."
    ssh "$PveUser@$PveHost" "zfs snapshot $ZfsSnapshotArgs &amp;&amp; zfs hold sqlsnap $ZfsSnapshotArgs"

    if ($LASTEXITCODE -ne 0) {
        throw "ZFS snapshot failed on $PveHost"
    }

    Write-Host "Writing SQL metadata backup..."

    Invoke-SqlNonQuery @"
BACKUP DATABASE &#091;$DbSafe]
TO DISK = N'$BackupFileSql'
WITH METADATA_ONLY,
     MEDIADESCRIPTION = N'$MediaSql',
     NAME = N'$SnapName';
"@

    Write-Host "Snapshot backup completed:"
    Write-Host "  Snapshot: $ZfsSnapshotArgs"
    Write-Host "  Metadata: $BackupFile"
}
catch {
    Write-Warning $_

    try {
        Write-Warning "Attempting to unfreeze SQL database..."
        Invoke-SqlNonQuery "ALTER DATABASE &#091;$DbSafe] SET SUSPEND_FOR_SNAPSHOT_BACKUP = OFF;"
    }
    catch {
        Write-Warning "Could not unfreeze cleanly. Check SQL Server error log."
    }

    throw
}
finally {
    $conn.Close()
}</code></pre>



<h2 class="wp-block-heading">Restore process</h2>



<p class="wp-block-paragraph">Here is how the restore process works:</p>



<ul class="wp-block-list">
<li>We connect to the corresponding SQL Server instance.</li>



<li>We take the database offline.</li>



<li>The volume dedicated to the StackOverflow database is taken offline.</li>



<li>We connect to the hypervisor through SSH.</li>



<li>We roll back the corresponding snapshot.</li>



<li>We restore the database using the corresponding backup, which was created at the same time as the snapshot.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="627" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-1024x627.png" alt="" class="wp-image-44501" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-1024x627.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-300x184.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-768x470.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-1536x941.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-51-2048x1254.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Powershell implementation (restore)</h2>



<p class="wp-block-paragraph">Here is the code used to perform the restore:</p>



<pre class="wp-block-code"><code>param(
    &#091;string]$SqlInstance = "VM-WS25-SQL2",
    &#091;string]$Database    = "StackOverflow",
    &#091;string]$BackupFile  = "D:\Backups\StackOverflow_20260514T122642.bkm",
    &#091;string]$SnapName    = "sql_StackOverflow_20260514T122642",
    &#091;string]$PveHost     = "192.168.1.110",
    &#091;string]$PveUser     = "MyUser",
    &#091;string&#091;]]$Zvols     = @("sqlpool/pve/vm-302-disk-0"),
    &#091;string&#091;]]$DatabaseDriveLetters = @("T"),
    &#091;switch]$NoRecovery
)

$ErrorActionPreference = "Stop"

function Assert-SafeName {
    param(
        &#091;string]$Value,
        &#091;string]$Name,
        &#091;string]$Pattern
    )

    if ($Value -notmatch $Pattern) {
        throw "$Name contained not allowed characters : $Value"
    }
}

function Normalize-DriveLetter {
    param(&#091;string]$DriveLetter)

    $letter = $DriveLetter.Trim().TrimEnd(":").ToUpperInvariant()

    if ($letter -notmatch '^&#091;A-Z]$') {
        throw "Drive letter invalid : $DriveLetter"
    }

    return $letter
}

function Get-DiskForDriveLetter {
    param(&#091;string]$DriveLetter)

    $letter = Normalize-DriveLetter $DriveLetter

    $partition = Get-Partition -DriveLetter $letter -ErrorAction Stop
    $disk = $partition | Get-Disk -ErrorAction Stop

    return &#091;pscustomobject]@{
        DriveLetter = $letter
        DiskNumber  = &#091;int]$disk.Number
        IsOffline   = &#091;bool]$disk.IsOffline
        FriendlyName = $disk.FriendlyName
        Size        = $disk.Size
    }
}

function Invoke-SshChecked {
    param(&#091;string]$Command)

    Write-Host "SSH $PveUser@$PveHost :: $Command"

    &amp; ssh "$PveUser@$PveHost" "$Command"

    if ($LASTEXITCODE -ne 0) {
        throw "SSH command failed with code $LASTEXITCODE : $Command"
    }
}

function New-SqlConnection {
    $connString = "Server=$SqlInstance;Database=master;Integrated Security=True;TrustServerCertificate=True;Application Name=ZFS-TSQL-Restore-NoVmRestart;"
    return New-Object System.Data.SqlClient.SqlConnection $connString
}

function Invoke-SqlNonQuery {
    param(&#091;string]$Sql)

    $conn = New-SqlConnection

    try {
        $conn.Open()
        $cmd = $conn.CreateCommand()
        $cmd.CommandTimeout = 0
        $cmd.CommandText = $Sql
        &#091;void]$cmd.ExecuteNonQuery()
    }
    finally {
        $conn.Close()
    }
}

function Invoke-SqlScalar {
    param(&#091;string]$Sql)

    $conn = New-SqlConnection

    try {
        $conn.Open()
        $cmd = $conn.CreateCommand()
        $cmd.CommandTimeout = 0
        $cmd.CommandText = $Sql
        return $cmd.ExecuteScalar()
    }
    finally {
        $conn.Close()
    }
}

function Set-DatabaseDisksOffline {
    param(&#091;object&#091;]]$DiskInfos)

    $offlinedByScript = @()

    foreach ($diskInfo in ($DiskInfos | Sort-Object DiskNumber -Unique)) {
        if ($diskInfo.IsOffline) {
            Write-Host "Disque $($diskInfo.DiskNumber) déjà offline. Lecteur $($diskInfo.DriveLetter):"
            continue
        }

        Write-Host "Taking the Windows disk offline $($diskInfo.DiskNumber), drive $($diskInfo.DriveLetter):"
        Set-Disk -Number $diskInfo.DiskNumber -IsOffline $true

        $offlinedByScript += $diskInfo
    }

    return $offlinedByScript
}

function Set-DatabaseDisksOnline {
    param(&#091;object&#091;]]$DiskInfos)

    foreach ($diskInfo in ($DiskInfos | Sort-Object DiskNumber -Unique)) {
        Write-Host "Bringing the Windows disk back online. $($diskInfo.DiskNumber), drive $($diskInfo.DriveLetter):"
        Set-Disk -Number $diskInfo.DiskNumber -IsOffline $false
    }

    Write-Host "Update-HostStorageCache..."
    Update-HostStorageCache
}

Assert-SafeName -Value $SnapName -Name "SnapName" -Pattern '^&#091;A-Za-z0-9_.:-]{1,160}$'

foreach ($zvol in $Zvols) {
    Assert-SafeName -Value $zvol -Name "Zvol" -Pattern '^&#091;A-Za-z0-9_.:/-]{1,240}$'
}

$DbQuoted = "&#091;" + $Database.Replace("]", "]]") + "]"
$DbLiteral = $Database.Replace("'", "''")
$BackupFileSql = $BackupFile.Replace("'", "''")

$ZfsSnapshots = $Zvols | ForEach-Object { "$_@$SnapName" }
$ZfsSnapshotArgs = ($ZfsSnapshots | ForEach-Object { "'$_'" }) -join " "

$RecoveryOption = if ($NoRecovery) { "NORECOVERY" } else { "RECOVERY" }

$DatabaseDiskInfos = @()
$DisksOfflinedByScript = @()

Write-Host ""
Write-Host "Restore SQL Server from a ZFS snapshot, without restarting the VM"
Write-Host "SQL Instance : $SqlInstance"
Write-Host "Database     : $Database"
Write-Host "BackupFile   : $BackupFile"
Write-Host "DB volumes   : $($DatabaseDriveLetters -join ', ')"
Write-Host "Snapshots    :"
$ZfsSnapshots | ForEach-Object { Write-Host "  $_" }
Write-Host ""

try {
    Write-Host "Checking ZFS snapshots..."
    Invoke-SshChecked "zfs list -H -t snapshot -o name $ZfsSnapshotArgs &gt;/dev/null"

    Write-Host "Identifying Windows disks containing SQL Server files..."
    foreach ($driveLetter in $DatabaseDriveLetters) {
        $diskInfo = Get-DiskForDriveLetter $driveLetter
        $DatabaseDiskInfos += $diskInfo

        Write-Host "Drive $($diskInfo.DriveLetter): -&gt; Windows disk $($diskInfo.DiskNumber) &#091;$($diskInfo.FriendlyName)]"
    }

    $backupDrive = $null
    if ($BackupFile -match '^(&#091;A-Za-z]):\\') {
        $backupDrive = Normalize-DriveLetter $Matches&#091;1]

        try {
            $backupDiskInfo = Get-DiskForDriveLetter $backupDrive
            $targetDiskNumbers = @($DatabaseDiskInfos | ForEach-Object { $_.DiskNumber } | Select-Object -Unique)

            if ($targetDiskNumbers -contains $backupDiskInfo.DiskNumber) {
                throw @"
The backup file $BackupFile is located on drive $backupDrive, which is on the same Windows disk as the SQL Server data volume.
Taking the data disk offline would make the .bkm file inaccessible, and a rollback could also make the .bkm file disappear.
Move the .bkm file to C:, a network share, or another disk that is not rolled back.
"@
            }
        }
        catch {
            throw
        }
    }

    Write-Host "Checking whether the SQL Server database exists..."
    $DbExists = Invoke-SqlScalar "SELECT CASE WHEN DB_ID(N'$DbLiteral') IS NULL THEN 0 ELSE 1 END;"

    if ($DbExists -eq 1) {
        Write-Host "Taking database $Database OFFLINE..."
        Invoke-SqlNonQuery @"
ALTER DATABASE $DbQuoted SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE $DbQuoted SET OFFLINE WITH ROLLBACK IMMEDIATE;
"@
    }
    else {
        Write-Host "Database $Database does not exist in SQL Server. Continuing with disk offline and ZFS rollback."
    }

    Write-Host "Taking Windows disks containing MDF/LDF files offline..."
    $DisksOfflinedByScript = Set-DatabaseDisksOffline -DiskInfos $DatabaseDiskInfos

    Write-Host "Rolling back ZFS snapshot..."
    $RollbackCommands = ($ZfsSnapshots | ForEach-Object { "zfs rollback -r '$_'" }) -join "; "
    Invoke-SshChecked "set -e; $RollbackCommands"

    Write-Host "Bringing Windows disks back online..."
    Set-DatabaseDisksOnline -DiskInfos $DisksOfflinedByScript
    $DisksOfflinedByScript = @()

    Write-Host "Short pause to let Windows and SQL Server detect the restored disk state..."
    Start-Sleep -Seconds 5

    Write-Host "Restoring SQL Server metadata-only backup..."

    $RestoreSql = @"
RESTORE DATABASE $DbQuoted
FROM DISK = N'$BackupFileSql'
WITH METADATA_ONLY,
     REPLACE,
     $RecoveryOption;
"@

    Invoke-SqlNonQuery $RestoreSql

    if (-not $NoRecovery) {
        Write-Host "Setting database back to MULTI_USER..."
        Invoke-SqlNonQuery @"
ALTER DATABASE $DbQuoted SET MULTI_USER;
"@
    }

    Write-Host ""
    Write-Host "Restore completed."
    Write-Host "Database : $Database"
    Write-Host "Snapshot : $SnapName"
    Write-Host "Backup   : $BackupFile"
}
catch {
    Write-Warning "Restore failed: $_"

    if ($DisksOfflinedByScript.Count -gt 0) {
        try {
            Write-Warning "Attempting to bring disks offlined by the script back online..."
            Set-DatabaseDisksOnline -DiskInfos $DisksOfflinedByScript
            $DisksOfflinedByScript = @()
        }
        catch {
            Write-Warning "Unable to automatically bring the disks back online. Check with Get-Disk."
        }
    }

    try {
        $DbExistsAfterError = Invoke-SqlScalar "SELECT CASE WHEN DB_ID(N'$DbLiteral') IS NULL THEN 0 ELSE 1 END;"

        if ($DbExistsAfterError -eq 1 -and -not $NoRecovery) {
            Write-Warning "Attempting to set the database back ONLINE/MULTI_USER..."
            Invoke-SqlNonQuery @"
ALTER DATABASE $DbQuoted SET ONLINE;
ALTER DATABASE $DbQuoted SET MULTI_USER;
"@
        }
    }
    catch {
        Write-Warning "Unable to automatically set the database back ONLINE/MULTI_USER."
    }

    throw
}</code></pre>



<h2 class="wp-block-heading">What does it look like?</h2>



<p class="wp-block-paragraph">We start the backup process:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="530" height="82" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-52.png" alt="" class="wp-image-44503" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-52.png 530w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-52-300x46.png 300w" sizes="auto, (max-width: 530px) 100vw, 530px" /></figure>



<p class="wp-block-paragraph">We verify that the snapshot is present:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="750" height="131" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-53.png" alt="" class="wp-image-44504" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-53.png 750w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-53-300x52.png 300w" sizes="auto, (max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph">We verify that the backup is present:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="601" height="36" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-54.png" alt="" class="wp-image-44505" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-54.png 601w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-54-300x18.png 300w" sizes="auto, (max-width: 601px) 100vw, 601px" /></figure>



<p class="wp-block-paragraph">We drop the StackOverflow database:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="314" height="301" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-55.png" alt="" class="wp-image-44506" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-55.png 314w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-55-300x288.png 300w" sizes="auto, (max-width: 314px) 100vw, 314px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="310" height="231" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-56.png" alt="" class="wp-image-44507" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-56.png 310w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-56-300x224.png 300w" sizes="auto, (max-width: 310px) 100vw, 310px" /></figure>



<p class="wp-block-paragraph">We start the restore process:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="951" height="384" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-57.png" alt="" class="wp-image-44508" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-57.png 951w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-57-300x121.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-57-768x310.png 768w" sizes="auto, (max-width: 951px) 100vw, 951px" /></figure>



<p class="wp-block-paragraph">The database is available again. The restore took only a few seconds for a database of approximately 200 GB.</p>



<h2 class="wp-block-heading">Major drawbacks</h2>



<p class="wp-block-paragraph">In my case, the solution is executed from the SQL Server itself. Ideally, it should rather be hosted on another server or client machine. We could also imagine running these scripts from a scheduler such as RedDeck, for example.</p>



<p class="wp-block-paragraph">During the database restore, the database is switched to SINGLE_USER mode. This could be an issue if the applications using the database reconnect very frequently. A better approach would probably be to explicitly terminate the active sessions using the KILL command.</p>



<p class="wp-block-paragraph">We have also not yet covered the use of a REST API.</p>



<p class="wp-block-paragraph">Thank you. <a href="https://www.linkedin.com/in/amine-haloui-76968056/">Amine Haloui</a></p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-2-3/">SQL Server Snapshot Backup and Restore with Proxmox ZFS &#8211; Powershell implementation (2/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs-2-3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server Snapshot Backup and Restore with Proxmox ZFS (1/3)</title>
		<link>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs/</link>
					<comments>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs/#respond</comments>
		
		<dc:creator><![CDATA[Amine Haloui]]></dc:creator>
		<pubDate>Thu, 14 May 2026 21:26:03 +0000</pubDate>
				<category><![CDATA[Database management]]></category>
		<category><![CDATA[Hardware & Storage]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[proxmox]]></category>
		<category><![CDATA[Storage]]></category>
		<category><![CDATA[ZFS]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44439</guid>

					<description><![CDATA[<p>We are currently working with clients on migrations to SQL Server 2022 and SQL Server 2025. During a discussion with one client, we reviewed some of the benefits introduced in the latest SQL Server 2022 and 2025 releases. Among the available features, starting with SQL Server 2022, we have: Starting with SQL Server 2025: The [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs/">SQL Server Snapshot Backup and Restore with Proxmox ZFS (1/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">We are currently working with clients on migrations to SQL Server 2022 and SQL Server 2025. During a discussion with one client, we reviewed some of the benefits introduced in the latest SQL Server 2022 and 2025 releases.</p>



<p class="wp-block-paragraph">Among the available features, starting with SQL Server 2022, we have:</p>



<ul class="wp-block-list">
<li>T-SQL snapshot backup : <a href="https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/create-a-transact-sql-snapshot-backup?view=sql-server-ver17">https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/create-a-transact-sql-snapshot-backup?view=sql-server-ver17</a></li>
</ul>



<p class="wp-block-paragraph">Starting with SQL Server 2025:</p>



<ul class="wp-block-list">
<li>REST API Call through sp_invoke_external_rest_endpoint : <a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-invoke-external-rest-endpoint-transact-sql?view=sql-server-ver17&amp;tabs=request-headers">https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-invoke-external-rest-endpoint-transact-sql?view=sql-server-ver17&amp;tabs=request-headers</a></li>
</ul>



<p class="wp-block-paragraph">The customer’s environment consists of a very large number of instances, some of which host very large SQL Server databases. In this customer’s case, we are referring to a database of approximately 6–7 TB, configured for high availability using Always On Availability Groups. For this database, backups take around two hours, and restores take slightly longer.</p>



<p class="wp-block-paragraph">In addition, the customer has a Pure Storage array.</p>



<p class="wp-block-paragraph">We explained to the customer that it is possible to use certain SQL Server 2025 features together with their Pure Storage array to perform snapshots and restores very quickly.</p>



<p class="wp-block-paragraph">In summary, the process consists of performing the following operations:</p>



<ul class="wp-block-list">
<li>Change the database state to suspend writes.</li>



<li>Create the snapshot using the storage system.</li>



<li>Perform a backup using the BACKUP DATABASE MyDB WITH METADATA_ONLY command to indicate that a snapshot has been taken.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="231" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-36-1024x231.png" alt="" class="wp-image-44440" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-36-1024x231.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-36-300x68.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-36-768x173.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-36.png 1130w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Reference: <a href="https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/create-a-transact-sql-snapshot-backup?view=sql-server-ver17">https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/create-a-transact-sql-snapshot-backup?view=sql-server-ver17</a></p>



<p class="wp-block-paragraph">However, the customer raised several interesting questions, which, reading between the lines, can be summarized as follows:</p>



<ul class="wp-block-list">
<li>Can this also be applied to PostgreSQL?</li>



<li>Are we dependent on Pure Storage to achieve this?</li>
</ul>



<p class="wp-block-paragraph">Several articles have been published about the implementation of this process between SQL Server and Pure Storage including the following one:</p>



<ul class="wp-block-list">
<li><a href="https://www.nocentino.com/posts/2025-05-19-t-sql-rest-api-integration-in-sql-server-2025-streamlining-t-sql-snapshot-backups">https://www.nocentino.com/posts/2025-05-19-t-sql-rest-api-integration-in-sql-server-2025-streamlining-t-sql-snapshot-backups</a></li>
</ul>



<p class="wp-block-paragraph">In my opinion, it is possible to reproduce this operating model with other systems. In my case, we will use Proxmox and ZFS.</p>



<h2 class="wp-block-heading" id="h-context-and-environment">Context and environment</h2>



<p class="wp-block-paragraph">ZFS pool provides fast, storage-level, copy-on-write snapshots with minimal space overhead. This makes it well suited for SQL Server snapshot backups, where the database writes are briefly suspended while the underlying virtual disk is captured. ZFS also allows precise rollback or cloning of a snapshot, which is useful for both restore testing and recovery scenarios.</p>



<p class="wp-block-paragraph">On Proxmox, it integrates naturally with VM disks, making it a practical alternative to enterprise storage snapshot platforms.</p>



<p class="wp-block-paragraph">The environment consists of a server and two disks: one disk used to store the VMs, and a 1 TB Samsung T7 disk that will be used to create our ZFS pool.</p>



<h2 class="wp-block-heading" id="h-proxmox-setup">Proxmox Setup</h2>



<p class="wp-block-paragraph">We identity the path of the related volume (Samsung T7) :</p>



<pre class="wp-block-code"><code>for d in /dev/disk/by-id/*; do
&nbsp; &#091; "$(readlink -f "$d")" = "/dev/sda" ] &amp;&amp; echo "$d"
done</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="447" height="62" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-32.png" alt="" class="wp-image-44441" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-32.png 447w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-32-300x42.png 300w" sizes="auto, (max-width: 447px) 100vw, 447px" /></figure>



<p class="wp-block-paragraph">We create the pool. Everything stored in the disk will be erased :</p>



<pre class="wp-block-code"><code>DISK="/dev/disk/by-id/usb-Samsung_PSSD_T7_S6TWNJ0T300328F-0:0"

wipefs -a "$DISK"
sgdisk --zap-all "$DISK"
zpool create \
&nbsp; -o ashift=12 \
&nbsp; -o autotrim=on \
&nbsp; -O compression=lz4 \
&nbsp; -O atime=off \
&nbsp; -O xattr=sa \
&nbsp; -O acltype=posixacl \
&nbsp; -m /mnt/sqlpool \
&nbsp; sqlpool "$DISK"</code></pre>



<p class="wp-block-paragraph">Then we create a Proxmox dataset for the VM disks:</p>



<pre class="wp-block-code"><code>zfs create sqlpool/pve</code></pre>



<p class="wp-block-paragraph">We add it to proxmox:</p>



<pre class="wp-block-code"><code>pvesm add zfspool sql-zfs \
  --pool sqlpool/pve \
  --content images,rootdir \
  --sparse 1</code></pre>



<p class="wp-block-paragraph">We check the pool:</p>



<pre class="wp-block-code"><code>zpool status sqlpool

zfs list

pvesm status
pool: sqlpool
state: ONLINE

config:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATE&nbsp;&nbsp;&nbsp;&nbsp; READ WRITE CKSUM
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sqlpool&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ONLINE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;usb-Samsung_PSSD_T7_S6TWNJ0T300328F-0:0&nbsp;   ONLINE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp; 0

errors: No known data errors

NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; USED&nbsp; AVAIL&nbsp; REFER&nbsp; MOUNTPOINT
sqlpool&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 636K&nbsp;&nbsp; 899G&nbsp;&nbsp;&nbsp; 96K&nbsp; /mnt/sqlpool
sqlpool/pve&nbsp;&nbsp;&nbsp; 96K&nbsp;&nbsp; 899G&nbsp;&nbsp;&nbsp; 96K&nbsp; /mnt/sqlpool/pve

Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Type&nbsp;&nbsp;&nbsp;&nbsp; Status&nbsp;&nbsp;&nbsp;&nbsp; Total (KiB)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Used (KiB) Available (KiB)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; %
local&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dir&nbsp;&nbsp;&nbsp;&nbsp; active&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 98497780&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 42429080&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 51019152&nbsp;&nbsp; 43.08%
local-lvm&nbsp;&nbsp;&nbsp;&nbsp; lvmthin&nbsp;&nbsp;&nbsp;&nbsp; active&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3746553856&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 285112748&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3461441107&nbsp;&nbsp;&nbsp; 7.61%
sql-zfs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; zfspool&nbsp;&nbsp;&nbsp;&nbsp; active&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 942931428&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 96&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 942931332&nbsp;&nbsp;&nbsp; 0.00%</code></pre>



<p class="wp-block-paragraph">My VM ID is 302 and we have to add the virtual disk into the ZFS pool:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="251" height="203" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-37.png" alt="" class="wp-image-44446" /></figure>



<pre class="wp-block-code"><code>VMID=302
qm set "$VMID" --agent enabled=1
qm set "$VMID" --scsihw virtio-scsi-single
qm set "$VMID" --scsi1 sql-zfs:700,cache=none,discard=on,iothread=1,ssd=1</code></pre>



<p class="wp-block-paragraph">Be carefull to the scsi ID. You may overwrite a used volume.</p>



<h2 class="wp-block-heading" id="h-what-does-it-look-like">What does it look like ?</h2>



<p class="wp-block-paragraph">Once the pool created we have something like this :</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="375" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40-1024x375.png" alt="" class="wp-image-44450" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40-1024x375.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40-300x110.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40-768x281.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40-1536x562.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-40.png 1614w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">On the virtual machine side, I have 3 disks :</p>



<ul class="wp-block-list">
<li>1 for my virtual machine (for Windows Server)</li>



<li>1 for SQL Server</li>



<li>1 linked to the ZFS pool to store the user database (the StackOverflow database)</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="795" height="300" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-41.png" alt="" class="wp-image-44453" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-41.png 795w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-41-300x113.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-41-768x290.png 768w" sizes="auto, (max-width: 795px) 100vw, 795px" /></figure>



<h2 class="wp-block-heading" id="h-sql-server-setup">SQL Server setup</h2>



<p class="wp-block-paragraph">The virtual machine used for the tests runs with:</p>



<ul class="wp-block-list">
<li>Windows Server 2025 Standard Edition</li>



<li>SQL Server 2025 Enterprise Developer Edition</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="346" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-47-1024x346.png" alt="" class="wp-image-44457" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-47-1024x346.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-47-300x101.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-47-768x259.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-47.png 1403w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The mounted zvol is represented by the Databases (T:) volume. Most of the files related to the SQL Server installation are stored on the SQL (D:) volume while the StackOverflow database is located on the Databases (T:) volume.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="251" height="273" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-35.png" alt="" class="wp-image-44444" /></figure>



<h2 class="wp-block-heading" id="h-manual-process-flow-snapshot">Manual process flow (snapshot)</h2>



<p class="wp-block-paragraph">Here is how we will proceed to create a snapshot and then restore the database:</p>



<ul class="wp-block-list">
<li>ALTER DATABASE [StackOverflow] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON;</li>



<li>Create the snapshot using the zfs snapshot command.</li>



<li>Run BACKUP DATABASE [StackOverflow] &#8230; WITH METADATA_ONLY.</li>
</ul>



<p class="wp-block-paragraph">To avoid confusion and to be able to link the snapshot to the backup, we will include the snapshot name in the MEDIADESCRIPTION clause.</p>



<p class="wp-block-paragraph">Here are the corresponding commands to create the snapshot:</p>



<pre class="wp-block-code"><code>ALTER DATABASE &#091;StackOverflow] SET SUSPEND_FOR_SNAPSHOT_BACKUP = ON;</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="979" height="150" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-34.png" alt="" class="wp-image-44443" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-34.png 979w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-34-300x46.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-34-768x118.png 768w" sizes="auto, (max-width: 979px) 100vw, 979px" /></figure>



<p class="wp-block-paragraph">We perform the snapshot:</p>



<pre class="wp-block-code"><code>zfs snapshot sqlpool/pve/vm-302-disk-0@StackOverflow_11052026_235500</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="646" height="18" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-39.png" alt="" class="wp-image-44447" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-39.png 646w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-39-300x8.png 300w" sizes="auto, (max-width: 646px) 100vw, 646px" /></figure>



<p class="wp-block-paragraph">In the same session as the ALTER DATABASE command, we perform a backup:</p>



<pre class="wp-block-code"><code>BACKUP DATABASE &#091;StackOverflow]
TO DISK = N'D:\Backups\StackOverflow_11052026_235500.bkm'
WITH METADATA_ONLY, MEDIADESCRIPTION = N'zfs|proxmox1|sqlpool/pve/vm-302-disk-0@StackOverflow_11052026_235500';</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="888" height="229" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-38.png" alt="" class="wp-image-44448" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-38.png 888w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-38-300x77.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-38-768x198.png 768w" sizes="auto, (max-width: 888px) 100vw, 888px" /></figure>



<p class="wp-block-paragraph">The error log shows the following:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="57" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-31-1024x57.png" alt="" class="wp-image-44445" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-31-1024x57.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-31-300x17.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-31-768x43.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-31.png 1385w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">We verify that the snapshot has been successfully created:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="745" height="118" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-42.png" alt="" class="wp-image-44449" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-42.png 745w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-42-300x48.png 300w" sizes="auto, (max-width: 745px) 100vw, 745px" /></figure>



<p class="wp-block-paragraph">And the SQL backup :</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="633" height="148" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-33.png" alt="" class="wp-image-44442" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-33.png 633w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-33-300x70.png 300w" sizes="auto, (max-width: 633px) 100vw, 633px" /></figure>



<h2 class="wp-block-heading" id="h-manual-process-flow-restore">Manual process flow (restore)</h2>



<p class="wp-block-paragraph">We now need to be able to restore the database. Before doing so, we can delete a few tables to verify that the database has been restored as expected. We deleted most of the tables, leaving only three:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="227" height="331" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-45.png" alt="" class="wp-image-44454" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-45.png 227w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-45-206x300.png 206w" sizes="auto, (max-width: 227px) 100vw, 227px" /></figure>



<p class="wp-block-paragraph">To perform the restore, we will follow these steps:</p>



<ul class="wp-block-list">
<li>Take the database offline.</li>



<li>Rollback the snapshot using the zfs rollback command.</li>



<li>Restore the database using the SQL backup created earlier.</li>
</ul>



<p class="wp-block-paragraph">This is done using the following commands:</p>



<pre class="wp-block-code"><code>ALTER DATABASE &#091;StackOverflow] SET OFFLINE WITH ROLLBACK IMMEDIATE;</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="979" height="203" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-46.png" alt="" class="wp-image-44455" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-46.png 979w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-46-300x62.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-46-768x159.png 768w" sizes="auto, (max-width: 979px) 100vw, 979px" /></figure>



<p class="wp-block-paragraph">Snapshot restore:</p>



<pre class="wp-block-code"><code>zfs rollback -r sqlpool/pve/vm-302-disk-0@StackOverflow_13052026_230000</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="661" height="18" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-44.png" alt="" class="wp-image-44452" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-44.png 661w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-44-300x8.png 300w" sizes="auto, (max-width: 661px) 100vw, 661px" /></figure>



<p class="wp-block-paragraph">Database restore:</p>



<pre class="wp-block-code"><code>RESTORE DATABASE &#091;StackOverflow]
FROM DISK = N'D:\Backups\StackOverflow_13052026_230000.bkm'
WITH METADATA_ONLY, REPLACE, NORECOVERY;

RESTORE DATABASE &#091;StackOverflow] WITH RECOVERY;</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="918" height="445" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-43.png" alt="" class="wp-image-44451" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-43.png 918w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-43-300x145.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-43-768x372.png 768w" sizes="auto, (max-width: 918px) 100vw, 918px" /></figure>



<p class="wp-block-paragraph"><strong>We were able to restore our database in less than one second, even though it is approximately 207 GB in size.</strong></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="147" height="40" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/05/image-48.png" alt="" class="wp-image-44456" /></figure>



<h2 class="wp-block-heading" id="h-major-drawbacks">Major drawbacks</h2>



<p class="wp-block-paragraph">The process is manual, and we need to switch between running commands in SQL Server and performing the snapshot/restore operations in Proxmox. This freezes the database for a certain amount of time. During that period, connected applications could generate errors or timeouts.</p>



<p class="wp-block-paragraph">The solution to this problem would be to automate the process using PowerShell, for example.</p>



<h2 class="wp-block-heading" id="h-what-was-not-covered-in-this-section">What was not covered in this section</h2>



<p class="wp-block-paragraph">While writing this blog post, I omitted two points:</p>



<ul class="wp-block-list">
<li>When the database is deleted, it is necessary to take the volume dedicated to the StackOverflow database, Databases (D:), offline. Indeed, When you run a DROP DATABASE, SQL Server deletes the files from disk, and the database no longer exists. Then, if you perform a zfs rollback while Windows still sees the disk as online, you are effectively changing the disk “under Windows feet” Windows may keep the previous NTFS state cached: an empty directory, MFT information, file handles, volume metadata, and so on. As a result, the ZFS rollback may have completed successfully, but Windows does not properly refresh its view of the disk.</li>



<li>We did not make any calls to a REST API. Indeed, this functionality does not exist in my case, but it is possible to implement it.</li>
</ul>



<p class="wp-block-paragraph">Thank you. <a href="https://www.linkedin.com/in/amine-haloui-76968056/">Amine Haloui</a></p>
<p>L’article <a href="https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs/">SQL Server Snapshot Backup and Restore with Proxmox ZFS (1/3)</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/sql-server-snapshot-backup-and-restore-with-proxmox-zfs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Overcoming RDP connection issues on Windows 11</title>
		<link>https://www.dbi-services.com/blog/overcoming-rdp-connection-issues-on-windows11/</link>
					<comments>https://www.dbi-services.com/blog/overcoming-rdp-connection-issues-on-windows11/#respond</comments>
		
		<dc:creator><![CDATA[Rémy Gaudey]]></dc:creator>
		<pubDate>Wed, 04 Feb 2026 15:23:03 +0000</pubDate>
				<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[MobaXterm]]></category>
		<category><![CDATA[RDP]]></category>
		<category><![CDATA[Windows 11]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=42789</guid>

					<description><![CDATA[<p>The problem: I faced an issue today where one of my colleagues was struggling to remotely connect to a Windows server via RDP. He was using the default Remote Desktop app that comes standard with Windows 11 and everytime he&#8217;d connect, the application would hang at login. Something between 2 to 4 minutes, until he [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/overcoming-rdp-connection-issues-on-windows11/">Overcoming RDP connection issues on Windows 11</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-the-problem">The problem:</h2>



<p class="wp-block-paragraph">I faced an issue today where one of my colleagues was struggling to remotely connect to a Windows server via RDP.</p>



<p class="wp-block-paragraph">He was using the default Remote Desktop app that comes standard with Windows 11 and everytime he&#8217;d connect, the application would hang at login. Something between 2 to 4 minutes, until he could finally type his password and access his remote machine.</p>



<p class="wp-block-paragraph">Even after checking our firewall rules inside out, tracing his connections, nothing obvious could be found.</p>



<h2 class="wp-block-heading" id="h-the-checks">The checks:</h2>



<p class="wp-block-paragraph">I then decided to run a bunch of tests:</p>



<ul class="wp-block-list">
<li>1st test &#8211; Connection from a Mac:</li>
</ul>



<p class="wp-block-paragraph">I tried accessing the remote server from my Mac using &#8220;Windows App&#8221;, no issues at all. The connection to the remote host works like a charm.</p>



<ul class="wp-block-list">
<li>2st test &#8211; Connection from an old laptop running Windows10:</li>
</ul>



<p class="wp-block-paragraph">We still had an old laptop running Windows10 in the office, I wanted to give it a try. There again, no issue. The connection is lightning fast.</p>



<p class="wp-block-paragraph">Even chatGPT was about to give up, after suggesting in turn, to check:<br>&#8211; IPv6 timeout<br>&#8211; Disable auto-redirect of local devices (printers, drives, smart cards&#8230;)<br>&#8211; Reverse DNS lookup delay<br>&#8211; Kerberos delay<br>&#8211; Windows Defender<br>&#8211; Power throttling<br>&#8211; &#8230;</p>



<p class="wp-block-paragraph">It turns out that none of these suggestions actually worked.</p>



<p class="wp-block-paragraph">As I am writing this article, I am still not sure what the root cause is. For sure, it is the Windows Remote Desktop application that either waits for a timeout of some sort or another child process preventing Remote Desktop to initiate the connection in a timely manner.</p>



<p class="wp-block-paragraph">I&#8217;d put my 2 cents on Windows Security having to do with the problem, but I can&#8217;t really prove it at this stage.</p>



<h2 class="wp-block-heading" id="h-the-solution">The Solution:</h2>



<p class="wp-block-paragraph">Just do not use Remote Desktop with Windows 11 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br>I downloaded <a href="https://mobaxterm.mobatek.net/download.html">MobaXterm</a>, opened a RDP connection to my remote host and it worked right away.</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Yes, sometimes you just spend a lot of time trying to fix stuff&#8230; until you realize there isn&#8217;t much you can do about it.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/overcoming-rdp-connection-issues-on-windows11/">Overcoming RDP connection issues on Windows 11</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/overcoming-rdp-connection-issues-on-windows11/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Shell and Perl in one script</title>
		<link>https://www.dbi-services.com/blog/shell-and-perl-in-one-script/</link>
					<comments>https://www.dbi-services.com/blog/shell-and-perl-in-one-script/#respond</comments>
		
		<dc:creator><![CDATA[Martin Bracher]]></dc:creator>
		<pubDate>Fri, 05 Dec 2025 10:19:04 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[script]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[Workshop]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=41480</guid>

					<description><![CDATA[<p>To run a Perl script directly like a binary from within the shell, you have to the 2nd and 3rd example will run the script with the given Perl-binary. The 1st example uses the 1st perl binary found in $PATH If you work with Oracle and want to use a Perl script connecting to the [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/shell-and-perl-in-one-script/">Shell and Perl in one script</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">To run a Perl script directly like a binary from within the shell, you have to </p>



<ul class="wp-block-list">
<li>set the executable flag on the script</li>



<li>Add the shebang line (the 1st line (<code>#!...</code>) specifiyng the interpreter to use)</li>
</ul>



<pre class="wp-block-code"><code>#!/usr/bin/env perl
#!/usr/bin/perl
#!/u01/app/oracle/product/19/perl/bin/perl</code></pre>



<p class="wp-block-paragraph">the 2nd and 3rd example will run the script with the given Perl-binary. </p>



<p class="wp-block-paragraph">The 1st example uses the 1st perl binary found in $PATH</p>



<p class="wp-block-paragraph">If you work with Oracle and want to use a Perl script connecting to the database, then the Perl from the OS (/usr/bin/perl) is usually not usable because the Perl-modules for Oracle (DBI / DBD::Oracle) are missing.</p>



<p class="wp-block-paragraph">A solution could be to use the line of the 3rd example, but then, the script is hardcoded to only that ORACLE_HOME.</p>



<p class="wp-block-paragraph">As a solution, often a shell-wrapper script  is used to call the perl-script with the correct perl binary (both scripts are in the same directory and have the same name, but different suffixes: .sh and .pl)</p>



<pre class="wp-block-code"><code>#!/usr/bin/env bash
cd $(dirname `command -v $0`)  # change to script-directory
$ORACLE_HOME/perl/bin/perl $(basename $0 .sh).pl</code></pre>



<p class="wp-block-paragraph">But is it also possible to integrate both scripts in one file? Yes, it is possible.</p>



<pre class="wp-block-code"><code>#!/usr/bin/env bash
echo "hello shell"
# additional code, e.g. setting the env. for a specific ORACLE_SID

# Hand off to the embedded Perl section in this file:
exec $ORACLE_HOME/perl/bin/perl -x "$0" "$@"

#!perl
# the line above is essential to detect the begin of Perl-code
# Everything below this line is treated as Perl
print "hello perl\n";
use DBD::Oracle;  #if there is an error, the wrong Perl is used</code></pre>



<p class="wp-block-paragraph">And the output is as expected:</p>



<pre class="wp-block-code"><code>hello shell
hello perl</code></pre>



<p class="wp-block-paragraph">Both &#8220;hello *&#8221; appear and no error that DBD::Oracle is not found, because we use Oracle&#8217;s Perl.</p>



<p class="wp-block-paragraph">And what happens, if we call this script with Perl? </p>



<pre class="wp-block-code"><code># perl shell1.sh
hello shell
hello perl</code></pre>



<p class="wp-block-paragraph">For me, an unexpected behavior. Perl does not complain about the shell code (unknown syntax), it executes it as shell code according the shebang-line (#!/usr/bin/env bash).</p>



<p class="wp-block-paragraph">But how can we call the script as a real Perl-script, without executing the shell-Part? We can hide it with the Perl Old Documentation tags (=pod / =cut). The shell will complain and ignore the unknown POD line and continues with the next one. Perl will skip this block from parsing:</p>



<pre class="wp-block-code"><code>=pod 2&gt;/dev/null
echo "hello shell"
exec $ORACLE_HOME/perl/bin/perl -x "$0" "$@"
=cut

#!perl
print "hello perl\n";
use DBD::Oracle;</code></pre>



<p class="wp-block-paragraph">Little disadvantage: We can no longer use the shebang line for the shell. The script uses the shell from which it was invoked. To explicitly use a specific shell, we have to call the script with the shell: <code>bash script2.sh</code></p>



<pre class="wp-block-code"><code>perl script2.sh   # perl references to /usr/bin/perl
Can't locate DBD/Oracle.pm in @INC (...</code></pre>



<p class="wp-block-paragraph">OK, the script is really executed with the given Perl (which does not contain DBD::Oracle). Now, try it with the Oracle Perl.</p>



<pre class="wp-block-code"><code>$ORACLE_HOME/perl/bin/perl scrip2.sh
hello perl</code></pre>



<p class="wp-block-paragraph">And success! Perl skips the shell-code and only runs the Perl-code.</p>



<p class="wp-block-paragraph">So, we have a script that can be used as a &#8220;normal&#8221; Perl-Script, and as a wrapper-script without maintaining 2 separate scripts.</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/shell-and-perl-in-one-script/">Shell and Perl in one script</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/shell-and-perl-in-one-script/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Patching SUSE Multi Linux Manager</title>
		<link>https://www.dbi-services.com/blog/patching-suse-multi-linux-manager/</link>
					<comments>https://www.dbi-services.com/blog/patching-suse-multi-linux-manager/#respond</comments>
		
		<dc:creator><![CDATA[Daniel Westermann]]></dc:creator>
		<pubDate>Wed, 30 Jul 2025 09:10:18 +0000</pubDate>
				<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[SUSE]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Multi Linux]]></category>
		<category><![CDATA[SUMA]]></category>
		<category><![CDATA[SuSE]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=39759</guid>

					<description><![CDATA[<p>In the last post about SUSE Multi Linux Manager we had a look at how you can schedule OpenSCAP reports using the API. In this post we&#8217;ll look into something very basic: How can you patch the server components of SUSE Multi Linux Manager. We speak about components because you need to patch the host [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/patching-suse-multi-linux-manager/">Patching SUSE Multi Linux Manager</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In the last post about SUSE Multi Linux Manager we had a look at how you can schedule <a href="https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/" target="_blank" rel="noreferrer noopener">OpenSCAP reports using the API</a>. In this post we&#8217;ll look into something very basic: How can you patch the server components of SUSE Multi Linux Manager. We speak about components because you need to patch the host (which is a <a href="https://www.suse.com/products/micro/" target="_blank" rel="noreferrer noopener">SLE Micro</a> in this case) and the container hosting the application.</p>



<p class="wp-block-paragraph">Looking at the host operating system we can see this is a SLE Micro 5.5:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1]; title: ; notranslate">
suma:~ $ cat /etc/os-release 
NAME=&quot;SLE Micro&quot;
VERSION=&quot;5.5&quot;
VERSION_ID=&quot;5.5&quot;
PRETTY_NAME=&quot;SUSE Linux Enterprise Micro 5.5&quot;
ID=&quot;sle-micro&quot;
ID_LIKE=&quot;suse&quot;
ANSI_COLOR=&quot;0;32&quot;
CPE_NAME=&quot;cpe:/o:suse:sle-micro:5.5&quot;
</pre></div>


<p class="wp-block-paragraph">As this comes with a read only root file system we cannot directly use <a href="https://documentation.suse.com/smart/systems-management/html/concept-zypper/index.html" target="_blank" rel="noreferrer noopener">zypper</a> to patch the system. The tool to use in this case is <a href="https://documentation.suse.com/smart/systems-management/html/Micro-transactional-updates/index.html" target="_blank" rel="noreferrer noopener">transactional-update</a>. This still uses zypper in the background, but the updates are installed into a new <a href="https://en.wikipedia.org/wiki/Btrfs" target="_blank" rel="noreferrer noopener">Btrfs</a> snapshot. Using this approach the running system is not touched at all and the updates only become available when the system is rebooted into the new snapshot (which happens automatically when the system is rebooted). If something is wrong with the new snapshot, the system can be booted from the old snapshot and the system is back to what it was before patching.</p>



<p class="wp-block-paragraph">Before we patch the host system let&#8217;s have a look at the snapshots we currently have available:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1]; title: ; notranslate">
suma:~ $ snapper list
 # | Type   | Pre # | Date                             | User | Used Space | Cleanup | Description           | Userdata     
---+--------+-------+----------------------------------+------+------------+---------+-----------------------+--------------
0  | single |       |                                  | root |            |         | current               |              
1  | single |       | Fri 08 Mar 2024 10:45:41 AM CET  | root |   1.30 GiB | number  | first root filesystem | important=yes
2  | single |       | Mon 07 Jul 2025 12:18:08 PM CEST | root |   1.51 MiB | number  | Snapshot Update of #1 | important=yes
3  | single |       | Mon 07 Jul 2025 12:30:01 PM CEST | root |   1.02 MiB | number  | Snapshot Update of #2 | important=yes
4  | single |       | Tue 08 Jul 2025 05:33:39 AM CEST | root |  39.78 MiB | number  | Snapshot Update of #3 | important=yes
5  | single |       | Wed 16 Jul 2025 09:25:23 AM CEST | root |  45.07 MiB |         | Snapshot Update of #4 |              
6* | single |       | Wed 23 Jul 2025 04:13:09 PM CEST | root |  58.62 MiB |         | Snapshot Update of #5 |         
</pre></div>


<p class="wp-block-paragraph">Let&#8217;s patch and compare what we&#8217;ll have afterwards:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1,26]; title: ; notranslate">
suma:~ $ zypper ref
Warning: The gpg key signing file &#039;repomd.xml&#039; has expired.
  Repository:       SLE-Micro-5.5-Updates
  Key Fingerprint:  FEAB 5025 39D8 46DB 2C09 61CA 70AF 9E81 39DB 7C82
  Key Name:         SuSE Package Signing Key &lt;build@suse.de&gt;
  Key Algorithm:    RSA 2048
  Key Created:      Mon 21 Sep 2020 10:21:47 AM CEST
  Key Expires:      Fri 20 Sep 2024 10:21:47 AM CEST (EXPIRED)
  Rpm Name:         gpg-pubkey-39db7c82-5f68629b
Retrieving repository &#039;SLE-Micro-5.5-Updates&#039; metadata ..............................................................................................................&#x5B;done]
Building repository &#039;SLE-Micro-5.5-Updates&#039; cache ...................................................................................................................&#x5B;done]
Warning: The gpg key signing file &#039;repomd.xml&#039; has expired.
  Repository:       SUSE-Manager-Server-5.0-Updates
  Key Fingerprint:  FEAB 5025 39D8 46DB 2C09 61CA 70AF 9E81 39DB 7C82
  Key Name:         SuSE Package Signing Key &lt;build@suse.de&gt;
  Key Algorithm:    RSA 2048
  Key Created:      Mon 21 Sep 2020 10:21:47 AM CEST
  Key Expires:      Fri 20 Sep 2024 10:21:47 AM CEST (EXPIRED)
  Rpm Name:         gpg-pubkey-39db7c82-5f68629b
Retrieving repository &#039;SUSE-Manager-Server-5.0-Updates&#039; metadata ....................................................................................................&#x5B;done]
Building repository &#039;SUSE-Manager-Server-5.0-Updates&#039; cache .........................................................................................................&#x5B;done]
Repository &#039;SLE-Micro-5.5-Pool&#039; is up to date.                                                                                                                             
Repository &#039;SUSE-Manager-Server-5.0-Pool&#039; is up to date.                                                                                                                   
All repositories have been refreshed.

suma:~ $ transactional-update 
Checking for newer version.
transactional-update 4.1.9 started
Options: 
Separate /var detected.
2025-07-30 09:42:32 tukit 4.1.9 started
2025-07-30 09:42:32 Options: -c6 open 
2025-07-30 09:42:33 Using snapshot 6 as base for new snapshot 7.
2025-07-30 09:42:33 /var/lib/overlay/6/etc
2025-07-30 09:42:33 Syncing /etc of previous snapshot 5 as base into new snapshot &quot;/.snapshots/7/snapshot&quot;
2025-07-30 09:42:33 SELinux is enabled.
ID: 7
2025-07-30 09:42:36 Transaction completed.
Calling zypper up
2025-07-30 09:42:38 tukit 4.1.9 started
2025-07-30 09:42:38 Options: callext 7 zypper -R {} up -y --auto-agree-with-product-licenses 
2025-07-30 09:42:39 Executing `zypper -R /tmp/transactional-update-JsIr01 up -y --auto-agree-with-product-licenses`:
Refreshing service &#039;SUSE_Linux_Enterprise_Micro_5.5_x86_64&#039;.
Refreshing service &#039;SUSE_Manager_Server_Extension_5.0_x86_64&#039;.
Loading repository data...
Reading installed packages...

The following 21 packages are going to be upgraded:
  boost-license1_66_0 libboost_system1_66_0 libboost_thread1_66_0 libpolkit-agent-1-0 libpolkit-gobject-1-0 mgradm mgradm-bash-completion mgrctl mgrctl-bash-completion polkit python3-pyparsing python3-pytz python3-PyYAML python3-requests python3-salt python3-simplejson python3-urllib3 salt salt-minion salt-transactional-update uyuni-storage-setup-server

21 packages to upgrade.

Package download size:    16.8 MiB

Package install size change:
              |      71.4 MiB  required by packages that will be installed
   654.0 KiB  |  -   70.8 MiB  released by packages that will be removed

Backend:  classic_rpmtrans
Continue? &#x5B;y/n/v/...? shows all options] (y): y

...
2025-07-30 09:44:40 New default snapshot is #7 (/.snapshots/7/snapshot).
2025-07-30 09:44:40 Transaction completed.

Please reboot your machine to activate the changes and avoid data loss.
New default snapshot is #7 (/.snapshots/7/snapshot).
transactional-update finished
</pre></div>


<p class="wp-block-paragraph">As noted above we must reboot the system for the updates to become active. Before we do that, let&#8217;s again have a look at the snapshots:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1]; title: ; notranslate">
suma:~ $ snapper list
 # | Type   | Pre # | Date                             | User | Used Space | Cleanup | Description           | Userdata     
---+--------+-------+----------------------------------+------+------------+---------+-----------------------+--------------
0  | single |       |                                  | root |            |         | current               |              
1  | single |       | Fri 08 Mar 2024 10:45:41 AM CET  | root |   1.30 GiB | number  | first root filesystem | important=yes
2  | single |       | Mon 07 Jul 2025 12:18:08 PM CEST | root |   1.51 MiB | number  | Snapshot Update of #1 | important=yes
3  | single |       | Mon 07 Jul 2025 12:30:01 PM CEST | root |   1.02 MiB | number  | Snapshot Update of #2 | important=yes
4  | single |       | Tue 08 Jul 2025 05:33:39 AM CEST | root |  39.78 MiB | number  | Snapshot Update of #3 | important=yes
5  | single |       | Wed 16 Jul 2025 09:25:23 AM CEST | root |  45.07 MiB |         | Snapshot Update of #4 |              
6- | single |       | Wed 23 Jul 2025 04:13:09 PM CEST | root |   4.11 MiB |         | Snapshot Update of #5 |              
7+ | single |       | Wed 30 Jul 2025 09:42:32 AM CEST | root |  88.39 MiB |         | Snapshot Update of #6 |      
</pre></div>


<p class="wp-block-paragraph">We got a new snapshot (number 7) which is not yet active, let&#8217;s reboot and check again:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
suma:~ $ reboot
...
suma:~ $ snapper list
 # | Type   | Pre # | Date                             | User | Used Space | Cleanup | Description           | Userdata     
---+--------+-------+----------------------------------+------+------------+---------+-----------------------+--------------
0  | single |       |                                  | root |            |         | current               |              
1  | single |       | Fri 08 Mar 2024 10:45:41 AM CET  | root |   1.30 GiB | number  | first root filesystem | important=yes
2  | single |       | Mon 07 Jul 2025 12:18:08 PM CEST | root |   1.51 MiB | number  | Snapshot Update of #1 | important=yes
3  | single |       | Mon 07 Jul 2025 12:30:01 PM CEST | root |   1.02 MiB | number  | Snapshot Update of #2 | important=yes
4  | single |       | Tue 08 Jul 2025 05:33:39 AM CEST | root |  39.78 MiB | number  | Snapshot Update of #3 | important=yes
5  | single |       | Wed 16 Jul 2025 09:25:23 AM CEST | root |  45.07 MiB |         | Snapshot Update of #4 |              
6  | single |       | Wed 23 Jul 2025 04:13:09 PM CEST | root |   4.11 MiB |         | Snapshot Update of #5 |              
7* | single |       | Wed 30 Jul 2025 09:42:32 AM CEST | root |  88.39 MiB |         | Snapshot Update of #6 |            
</pre></div>


<p class="wp-block-paragraph">The new snapshot became active and we&#8217;re fully patched on the host system.</p>



<p class="wp-block-paragraph">Now that the host system is fully patched, we can proceed with patching the SUSE Multi Linux Manager application. Before we do that, let&#8217;s check what we currently have:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1]; title: ; notranslate">
suma:~ $ mgradm inspect
10:40AM INF Welcome to mgradm
10:40AM INF Executing command: inspect
10:40AM INF Computed image name is registry.suse.com/suse/manager/5.0/x86_64/server:5.0.4.1
10:40AM INF Ensure image registry.suse.com/suse/manager/5.0/x86_64/server:5.0.4.1 is available
WARN&#x5B;0002] Path &quot;/etc/SUSEConnect&quot; from &quot;/etc/containers/mounts.conf&quot; doesn&#039;t exist, skipping 
10:40AM INF 
{
  &quot;CurrentPgVersion&quot;: &quot;16&quot;,
  &quot;ImagePgVersion&quot;: &quot;16&quot;,
  &quot;DBUser&quot;: &quot;spacewalk&quot;,
  &quot;DBPassword&quot;: &quot;&lt;REDACTED&gt;&quot;,
  &quot;DBName&quot;: &quot;susemanager&quot;,
  &quot;DBPort&quot;: 5432,
  &quot;UyuniRelease&quot;: &quot;&quot;,
  &quot;SuseManagerRelease&quot;: &quot;5.0.4.1&quot;,
  &quot;Fqdn&quot;: &quot;suma.dwe.local&quot;
}
</pre></div>


<p class="wp-block-paragraph">The currently running version is &#8220;5.0.4.1&#8221;. Patching is quite simple as this just updates the container:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1]; title: ; notranslate">
suma:~ $ mgradm upgrade podman
10:41AM INF Welcome to mgradm
10:41AM INF Use of this software implies acceptance of the End User License Agreement.
10:41AM INF Executing command: podman
...
10:41AM INF No changes requested for hub. Keep 0 replicas.
10:41AM INF Computed image name is registry.suse.com/suse/manager/5.0/x86_64/server-hub-xmlrpc-api:5.0.5
10:41AM INF Ensure image registry.suse.com/suse/manager/5.0/x86_64/server-hub-xmlrpc-api:5.0.5 is available
10:42AM INF Cannot find RPM image for registry.suse.com/suse/manager/5.0/x86_64/server-hub-xmlrpc-api:5.0.5
</pre></div>


<p class="wp-block-paragraph">Checking the version again:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
suma:~ $ mgradm inspect
10:36AM INF Welcome to mgradm
10:36AM INF Use of this software implies acceptance of the End User License Agreement.
10:36AM INF Executing command: inspect
10:36AM INF Computed image name is registry.suse.com/suse/manager/5.0/x86_64/server:5.0.5
10:36AM INF Ensure image registry.suse.com/suse/manager/5.0/x86_64/server:5.0.5 is available
10:36AM ??? time=&quot;2025-07-30T10:36:20+02:00&quot; level=warning msg=&quot;Path \&quot;/etc/SUSEConnect\&quot; from \&quot;/etc/containers/mounts.conf\&quot; doesn&#039;t exist, skipping&quot;
10:36AM INF 
{
  &quot;CurrentPgVersion&quot;: &quot;16&quot;,
  &quot;ImagePgVersion&quot;: &quot;16&quot;,
  &quot;DBUser&quot;: &quot;spacewalk&quot;,
  &quot;DBPassword&quot;: &quot;&lt;REDACTED&gt;&quot;,
  &quot;DBName&quot;: &quot;susemanager&quot;,
  &quot;DBPort&quot;: 5432,
  &quot;UyuniRelease&quot;: &quot;&quot;,
  &quot;SuseManagerRelease&quot;: &quot;5.0.5&quot;,
  &quot;Fqdn&quot;: &quot;suma.dwe.local&quot;
}
</pre></div>


<p class="wp-block-paragraph">Now we are on version &#8220;5.0.5&#8221; and we&#8217;re done with our patching for the server part. Clients also should be upgraded, especially the <a href="https://github.com/saltstack/salt" target="_blank" rel="noreferrer noopener">Salt</a> client as SUSE Multi Linux Manager uses Salt to manage the clients. You can either do that manually by using the package manager of the distributions you&#8217;re managing or you can do that from the WebUI:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="529" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250730_104222-1024x529.png" alt="" class="wp-image-39768" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250730_104222-1024x529.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250730_104222-300x155.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250730_104222-768x397.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250730_104222.png 1488w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">That&#8217;s it, not hard to do and an easy process to follow.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/patching-suse-multi-linux-manager/">Patching SUSE Multi Linux Manager</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/patching-suse-multi-linux-manager/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Scheduling OpenSCAP reports in SUSE Multi-Linux Manager</title>
		<link>https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/</link>
					<comments>https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/#respond</comments>
		
		<dc:creator><![CDATA[Daniel Westermann]]></dc:creator>
		<pubDate>Wed, 23 Jul 2025 14:44:32 +0000</pubDate>
				<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SUSE]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[SUMA]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=39544</guid>

					<description><![CDATA[<p>As we&#8217;ve recently supported some customers on SUSE Multi Linux Manager I&#8217;d like share something which was not as easy to implement as it appeared to be in the first place. But first of all, what is SUSE Multi Linux Manager? It is basically a fork of Spacewalk which was also used as the upstream [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/">Scheduling OpenSCAP reports in SUSE Multi-Linux Manager</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">As we&#8217;ve recently supported some customers on <a href="https://www.suse.com/products/multi-linux-manager/" target="_blank" rel="noreferrer noopener">SUSE Multi Linux Manager</a> I&#8217;d like share something which was not as easy to implement as it appeared to be in the first place. But first of all, what is SUSE Multi Linux Manager? It is basically a fork of <a href="https://spacewalkproject.github.io" target="_blank" rel="noreferrer noopener">Spacewalk</a> which was also used as the upstream project by the <a href="https://www.redhat.com/en/technologies/management/satellite" target="_blank" rel="noreferrer noopener">Red Hat Satellite</a> product. But as Spacewalk was dis-continued and the project on <a href="https://github.com/spacewalkproject/spacewalk" target="_blank" rel="noreferrer noopener">Github</a> was archived some people decided to fork in and started a new project called <a href="https://www.uyuni-project.org" target="_blank" rel="noreferrer noopener">Uyuni</a>, and this is now the upstream project for SUSE Multi Linux Manager. One of the great things about Uyuni and SUSE Multi Linux Manager is, that it supports various Linux distributions such as <a href="https://www.suse.com" target="_blank" rel="noreferrer noopener">SUSE</a> and <a href="https://www.opensuse.org" target="_blank" rel="noreferrer noopener">openSUSE</a> distributions, <a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux">Red Hat</a>, <a href="https://rockylinux.org" target="_blank" rel="noreferrer noopener">Rocky</a>, <a href="https://www.oracle.com/linux/" target="_blank" rel="noreferrer noopener">Oracle</a> and <a href="https://almalinux.org" target="_blank" rel="noreferrer noopener">Alma</a> Linux, <a href="https://www.debian.org">Debian</a>, <a href="https://ubuntu.com">Ubuntu</a>, and also ancient versions of <a href="https://centos.org">CentOS</a> if you still depend on them.</p>



<p class="wp-block-paragraph">I am not going into the setup or basic configuration as you can already find related bogs here and more information in the <a href="https://documentation.suse.com/suma/5.0/en/suse-manager/index.html" target="_blank" rel="noreferrer noopener">documentation</a>:</p>



<ul class="wp-block-list">
<li><a href="https://www.dbi-services.com/blog/uyuni-an-open-source-configuration-and-infrastructure-management-solution-for-software-defined-infrastructure-1-the-server/">Uyuni, an open-source configuration and infrastructure management solution for software-defined infrastructure (1) – The server</a> (this is for version 4.x)</li>



<li><a href="https://www.dbi-services.com/blog/uyuni-an-open-source-configuration-and-infrastructure-management-solution-for-software-defined-infrastructure-2-adding-a-client/">Uyuni, an open-source configuration and infrastructure management solution for software-defined infrastructure (2) – Adding a client</a> (this is for version 4.x)</li>



<li><a href="https://www.dbi-services.com/blog/suma-suse-manager-is-back-and-it-has-a-new-name-suse-multi-linux/" target="_blank" rel="noreferrer noopener">SUMA (SUSE Manager) is back and it has a new name: SUSE Multi-Linux</a></li>



<li><a href="https://www.dbi-services.com/blog/suse-manager-installation/" target="_blank" rel="noreferrer noopener">SUSE Manager installation</a> (this is for version 4)</li>
</ul>



<p class="wp-block-paragraph">What I want to look at in this post is automatic scheduling of <a href="https://www.open-scap.org/tools/openscap-base/" target="_blank" rel="noreferrer noopener">OpenSCAP</a> scans/reports. When this requirement came up, it seemed pretty easy to do, as you can easily schedule such a scan against a single system. As you can see below I have a Red Hat 9 system registered to my SUSE Multi Linux Server:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="460" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945-1024x460.png" alt="" class="wp-image-39653" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945-1024x460.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945-300x135.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945-768x345.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945-1536x690.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_155945.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">What you can easily do out of the box is to manually schedule an OpenSCAP scan:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="460" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301-1024x460.png" alt="" class="wp-image-39654" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301-1024x460.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301-300x135.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301-768x345.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301-1536x690.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160301.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Once the scan completes, it becomes visible under the &#8220;List Scan&#8221; tab and you can browse into the details:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="242" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1-1024x242.png" alt="" class="wp-image-39655" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1-1024x242.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1-300x71.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1-768x181.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1-1536x363.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160438-1.png 1898w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="372" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606-1024x372.png" alt="" class="wp-image-39656" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606-1024x372.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606-300x109.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606-768x279.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606-1536x559.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160606.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="372" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627-1024x372.png" alt="" class="wp-image-39657" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627-1024x372.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627-300x109.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627-768x279.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627-1536x559.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160627.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Quite easy to do but still a manual action. As we wanted to have it automated the obvious choice was to create a &#8220;Recurring Action&#8221;:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="176" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738-1024x176.png" alt="" class="wp-image-39658" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738-1024x176.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738-300x52.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738-768x132.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738-1536x264.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160738.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">This gives you to option to create and configure a &#8220;Recurring Action&#8221;:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="410" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933-1024x410.png" alt="" class="wp-image-39659" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933-1024x410.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933-300x120.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933-768x308.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933-1536x616.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_160933.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The issue is, there is no pre-defined &#8220;Custom State&#8221; which is scheduling an OpenSCAP scan:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="350" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042-1024x350.png" alt="" class="wp-image-39660" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042-1024x350.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042-300x103.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042-768x263.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042-1536x526.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161042.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The very same is true for &#8220;System Groups&#8221;, which you normally would use because otherwise you&#8217;d need to schedule that on every single system:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="154" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201-1024x154.png" alt="" class="wp-image-39661" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201-1024x154.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201-300x45.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201-768x116.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201-1536x232.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161201.png 1864w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The last option seemed to be something under &#8220;Schedule&#8221; but this only gives you a list of what you already have:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="376" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1-1024x376.png" alt="" class="wp-image-39662" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1-1024x376.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1-300x110.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1-768x282.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1-1536x563.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_161740-1.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">At this point we were stuck and had to talk to SUSE support, which really was a great experience by the way. It turned out there is no easy, build-in, way to do this. A feature request has been logged, but of course there is no guarantee that it will be implemented. </p>



<p class="wp-block-paragraph">But, there is a workaround, not a very beautiful one, but at least it works. SUSE Multi Linux Manager (and Uyuni of course) come with an <a href="https://documentation.suse.com/suma/5.0/api/suse-manager/index.html" target="_blank" rel="noreferrer noopener">API</a> and there is one call for triggering an <a href="https://documentation.suse.com/suma/5.0/api/suse-manager/api/system.scap.html" target="_blank" rel="noreferrer noopener">OpenSCAP scan</a>. Using this, a custom state channel can be created which in turn calls the API to trigger the scan:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="376" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705-1024x376.png" alt="" class="wp-image-39663" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705-1024x376.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705-300x110.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705-768x282.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705-1536x563.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162705.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="381" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934-1024x381.png" alt="" class="wp-image-39664" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934-1024x381.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934-300x112.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934-768x286.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934-1536x572.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_162934.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">The &#8220;SLS Contents&#8221; actually contains the code (Python in this case) which is taking to the API and triggers the scan:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [14]; title: ; notranslate">
/usr/local/bin/schedule_xccdf_scan.py:
  file.managed:
    - user: root
    - group: root
    - mode: 755
    - contents: |
        #!/usr/bin/python3
        import xmlrpc.client

        client = xmlrpc.client.ServerProxy(&#039;https://suma.dwe.local/rpc/api&#039;)
        key = client.auth.login(&#039;admin&#039;, &#039;xxxx&#039;)
        client.system.scap.scheduleXccdfScan(
            key,
            1000010000,
            &#039;/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml&#039;,
            &#039;--profile xccdf_org.ssgproject.content_profile_cis_server_l1&#039;
        )
        client.auth.logout(key)

schedule_xccdf_scan:
  cmd.run:
    - name: /usr/local/bin/schedule_xccdf_scan.py
    - require:
      - file: /usr/local/bin/schedule_xccdf_scan.py
</pre></div>


<p class="wp-block-paragraph">I am not going into the code itself, this should be easy to understand. The important part is the system ID in line 14. This defines the system you want the scan to happen on (you can also provide an array of systems, see the API documentation linked above).</p>



<p class="wp-block-paragraph">As soon as you have this, you can schedule this automatically as a recurring action on either the system itself, or a group of systems in &#8220;System Groups&#8221;:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="202" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519-1024x202.png" alt="" class="wp-image-39668" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519-1024x202.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519-300x59.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519-768x152.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519-1536x303.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163519.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="227" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558-1024x227.png" alt="" class="wp-image-39669" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558-1024x227.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558-300x67.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558-768x170.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558-1536x341.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163558.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="416" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655-1024x416.png" alt="" class="wp-image-39670" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655-1024x416.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655-300x122.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655-768x312.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655-1536x624.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/07/Screenshot_20250723_163655.png 1870w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Not as easy as it could be, and the systems are still hard coded in the Python code, but at least we have something that works. Hope that helps.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/">Scheduling OpenSCAP reports in SUSE Multi-Linux Manager</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/scheduling-openscap-reports-in-suse-multi-linux-manager/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Oracle DMK release 2.2.0 new features / Windows support.</title>
		<link>https://www.dbi-services.com/blog/oracle-dmk-release-2-2-0-new-features-windows-support/</link>
					<comments>https://www.dbi-services.com/blog/oracle-dmk-release-2-2-0-new-features-windows-support/#respond</comments>
		
		<dc:creator><![CDATA[Tomek Sikorski]]></dc:creator>
		<pubDate>Fri, 06 Jun 2025 08:05:21 +0000</pubDate>
				<category><![CDATA[Database Administration & Monitoring]]></category>
		<category><![CDATA[Database management]]></category>
		<category><![CDATA[Operating systems]]></category>
		<category><![CDATA[Oracle]]></category>
		<category><![CDATA[CDB]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[DMK]]></category>
		<category><![CDATA[dmk 2.2]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[multitenant]]></category>
		<category><![CDATA[PDB]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[windows server 2022]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=37773</guid>

					<description><![CDATA[<p>DMK is a Database Management Kit that can be used in various database environments. It simplifies command-line tasks for Databases Administrator on Unix and Windows systems. It supports multiple databases including Oracle, MongoDB, MariaDB, and PostgreSQL. It also includes additional modules for automating RMAN backups, database duplication, and database creation.dbi-services recently released version 2.2.0, introducing significant improvements for Oracle databases [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-dmk-release-2-2-0-new-features-windows-support/">Oracle DMK release 2.2.0 new features / Windows support.</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">DMK is a Database Management Kit that can be used in various database environments. </p>



<p class="wp-block-paragraph">It simplifies command-line tasks for Databases Administrator on Unix and Windows systems. It supports multiple databases including Oracle, MongoDB, MariaDB, and PostgreSQL. It also includes additional modules for automating RMAN backups, database duplication, and database creation.<br>dbi-services recently released version 2.2.0, introducing significant improvements for Oracle databases with RAC/ASM and enhanced support for Windows.</p>



<p class="wp-block-paragraph">You can check here more details:</p>



<p class="wp-block-paragraph"><a href="https://www.dbi-services.com/products/dmk-management-kit/">https://www.dbi-services.com/products/dmk-management-kit/</a></p>



<p class="wp-block-paragraph">Below example how its showing on command line processes output :</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="914" height="1024" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-914x1024.png" alt="" class="wp-image-37788" style="width:606px;height:auto" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-914x1024.png 914w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-268x300.png 268w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-768x860.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11.png 1314w" sizes="auto, (max-width: 914px) 100vw, 914px" /></figure>



<h2 class="wp-block-heading" id="h-windows-support">Windows support:</h2>



<p class="wp-block-paragraph">Now managing your Oracle databases on Windows server has never been easier.</p>



<p class="wp-block-paragraph">Just clicking DMK.cmd shortcut on your Desktop open you directly command line with DMK API.</p>



<p class="wp-block-paragraph">Using commands like istat/CDBNAME/lspdb we can easly navigate between our databases.</p>



<p class="wp-block-paragraph">With simple alias &#8216;u&#8217; we can see all databases on Windows server.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="537" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6-1024x537.png" alt="" class="wp-image-37780" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6-1024x537.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6-300x157.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6-768x403.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6-1536x805.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-6.png 1950w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ol start="1" class="wp-block-list">
<li></li>
</ol>



<p class="wp-block-paragraph">You can easily list or connect to your Multitenant PDBs just by using alias like lspdb. Use PDBNAME to directly login to your PDB:</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="892" height="1024" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-15-892x1024.png" alt="" class="wp-image-37802" style="width:621px;height:auto" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-15-892x1024.png 892w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-15-261x300.png 261w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-15-768x882.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-15.png 1230w" sizes="auto, (max-width: 892px) 100vw, 892px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="593" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7-1024x593.png" alt="" class="wp-image-37781" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7-1024x593.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7-300x174.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7-768x444.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7-1536x889.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-7.png 1970w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">in connection of DMK_backup and DMK_dbcreate DMK_dbduplicate its quite a powerful pack to manage your databases on Windows platform.</p>



<p class="wp-block-paragraph">Now syntax and output is fully compatible with Linux version</p>



<p class="wp-block-paragraph">This version also not need extra Perl installation, at it will use Perl binaries from Oracle Home on Unix and Windows OS.</p>



<p class="wp-block-paragraph">sqlplus/rman/asmcmd work with command history, simply use sqh and arrrow &#8216;up&#8217; and &#8216;down&#8217; to navigate over your previous selects:</p>



<p class="wp-block-paragraph">Usefull aliases:</p>



<p class="wp-block-paragraph"> taa &#8211; tail on alertlog</p>



<p class="wp-block-paragraph">cdh- move to current ORACLE_HOME</p>



<p class="wp-block-paragraph">cdd -move to DMK home</p>



<p class="wp-block-paragraph">sqh &#8211; sqlplus with history</p>



<p class="wp-block-paragraph">asmh -asmcmd with history</p>



<p class="wp-block-paragraph">rmanh &#8211; rman with history</p>



<p class="wp-block-paragraph">cddi &#8211; move to current DB DIAG_DEST</p>



<p class="wp-block-paragraph">vit/vil/vio &#8211; giving you fast access to edit your current tnsnames listener and oratab config without long time looking them all over filesystem.</p>



<p class="wp-block-paragraph">Full list of aliased you can find in DMK documentation under this <a href="https://dbi-services.gitbook.io/dmk-oracle-manual/installation-and-configuration/variables-and-aliases" target="_blank" rel="noreferrer noopener">link </a></p>



<p class="wp-block-paragraph">Below example of tail on alertlog:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="245" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-1024x245.png" alt="" class="wp-image-37783" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-1024x245.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-300x72.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-768x184.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-1536x368.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-8-2048x490.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Now also when your environment change:</p>



<p class="wp-block-paragraph">·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new ORACLE_HOME</p>



<p class="wp-block-paragraph">·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;changed ORACLE_HOME</p>



<p class="wp-block-paragraph">·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new/removed ORACLE_SID</p>



<p class="wp-block-paragraph">·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new/removed PDB&#8217;s</p>



<p class="wp-block-paragraph">DMK will automatically adjust informations and aliases.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="72" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10-1024x72.png" alt="" class="wp-image-37785" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10-1024x72.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10-300x21.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10-768x54.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10-1536x108.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-10.png 1880w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Now when you type &#8216;u&#8217; or SID, all your instances are refreshed from actual state also when some of database up/down status changed:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="846" height="1024" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9-846x1024.png" alt="" class="wp-image-37784" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9-846x1024.png 846w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9-248x300.png 248w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9-768x929.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9-1270x1536.png 1270w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-9.png 1458w" sizes="auto, (max-width: 846px) 100vw, 846px" /></figure>



<p class="wp-block-paragraph">These changes also apply to Linux version.</p>



<h2 class="wp-block-heading" id="h-oracle-rac-clusterware">Oracle RAC / Clusterware :</h2>



<p class="wp-block-paragraph">For Oracle with RAC/Clusterware configuration there are also some improvements. </p>



<p class="wp-block-paragraph">Use cstat to see crs resources in colored output. </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="914" height="1024" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-914x1024.png" alt="" class="wp-image-37788" style="width:759px;height:auto" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-914x1024.png 914w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-268x300.png 268w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11-768x860.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-11.png 1314w" sizes="auto, (max-width: 914px) 100vw, 914px" /></figure>



<p class="wp-block-paragraph">&nbsp;&nbsp;Also you can check your ASM instance:</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="824" height="516" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-14.png" alt="" class="wp-image-37792" style="width:454px;height:auto" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-14.png 824w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-14-300x188.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-14-768x481.png 768w" sizes="auto, (max-width: 824px) 100vw, 824px" /></figure>



<p class="wp-block-paragraph">What is also interesting for srvctl command we implemented command-completion with tab </p>



<p class="wp-block-paragraph">So you don&#8217;t need to think anymore about proper srvctl command arguments (and as you know its a lot of options). Now, just choose from the list using [tab]</p>



<p class="wp-block-paragraph">For example, stopping and starting database.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="479" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13-1024x479.png" alt="" class="wp-image-37790" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13-1024x479.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13-300x140.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13-768x359.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13-1536x719.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/03/image-13.png 1598w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">DMK not only helps to use command line commands , but it also helps to keep structural Oracle Optimal Flexible Architecture (OFA) standards of your naming convention/folder structure and keep Oracle best practices for database configuration. By installing additional modules for backup/duplication/dbcreate, you can provide instant standardization and improve quality for your environments.</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">You can read more in DMK documentation under this <a href="https://dbi-services.gitbook.io/dmk-oracle-manual" target="_blank" rel="noreferrer noopener">link.</a></p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/oracle-dmk-release-2-2-0-new-features-windows-support/">Oracle DMK release 2.2.0 new features / Windows support.</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/oracle-dmk-release-2-2-0-new-features-windows-support/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: www.dbi-services.com @ 2026-06-27 08:22:37 by W3 Total Cache
-->