<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Big Mazzy</title>
    <description>The latest articles on DEV Community by Big Mazzy (@big_mazzy_06d057cc24398c5).</description>
    <link>https://dev.to/big_mazzy_06d057cc24398c5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3875821%2F3c02d1c0-3728-4505-9773-ba2f0bcc8dd5.jpg</url>
      <title>DEV Community: Big Mazzy</title>
      <link>https://dev.to/big_mazzy_06d057cc24398c5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/big_mazzy_06d057cc24398c5"/>
    <language>en</language>
    <item>
      <title>Fix Only Console Users Are Allowed to Run the X Server</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Sun, 25 Oct 2026 13:30:50 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/fix-only-console-users-are-allowed-to-run-the-x-server-341f</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/fix-only-console-users-are-allowed-to-run-the-x-server-341f</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If the failing xrdp session uses the wrapper launcher, change the &lt;code&gt;[Xorg]&lt;/code&gt; executable parameter in &lt;code&gt;/etc/xrdp/sesman.ini&lt;/code&gt; to &lt;code&gt;/usr/lib/xorg/Xorg&lt;/code&gt;, then restart &lt;code&gt;xrdp-sesman&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/L8I0d9vk_qI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;An xrdp connection may fail with this message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/lib/xorg/Xorg.wrap: Only console users are allowed to run the X server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This denial can appear when a remote desktop session tries to start X through &lt;code&gt;Xorg.wrap&lt;/code&gt;. Before changing anything, confirm that the failing session actually uses that wrapper launcher. An SSH test can reproduce the denial, but by itself it does not prove which executable your remote desktop backend invokes.&lt;/p&gt;

&lt;p&gt;The steps below are for Ubuntu 22.04 and a legacy xrdp backend configured to use the wrapper. If your session does not use that launcher, stop here; this fix does not apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Xorg.wrap&lt;/code&gt; is a wrapper that restricts which users can start the X server. A remote desktop session may run under a user who is not considered a console user, so invoking the wrapper can produce the denial.&lt;/p&gt;

&lt;p&gt;The xrdp session manager’s &lt;code&gt;[Xorg]&lt;/code&gt; section specifies the executable used to start the graphical session. In the example configuration, that section has two &lt;code&gt;param&lt;/code&gt; lines. The first points to &lt;code&gt;/usr/bin/Xorg&lt;/code&gt;, which can be the wrapper launcher. Replacing only that executable parameter with the direct Xorg executable avoids the wrapper check for this backend. Preserve the remaining parameters in your actual configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the session manager configuration and find the &lt;code&gt;[Xorg]&lt;/code&gt; section. Confirm the failing session’s backend is configured to invoke the wrapper before proceeding.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;less /etc/xrdp/sesman.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="o"&gt;[&lt;/span&gt;Xorg]
   &lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/Xorg
   &lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib/xorg/Xorg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the executable in your own configuration; do not assume it matches the example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the launcher to see whether it points to &lt;code&gt;Xorg.wrap&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;grep &lt;/span&gt;Xorg.wrap /usr/bin/Xorg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test as the affected user over SSH, without &lt;code&gt;sudo&lt;/code&gt;. This example reproduces the denial:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   /usr/bin/Xorg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An SSH denial alone does not prove that the failing remote desktop backend uses the wrapper. If the session configuration does not use this launcher, stop rather than applying the change.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm that &lt;code&gt;xorgxrdp&lt;/code&gt; is installed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   dpkg &lt;span class="nt"&gt;-s&lt;/span&gt; xorgxrdp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected sample status is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Status: install ok installed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Edit the session manager configuration.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   sudoedit /etc/xrdp/sesman.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;[Xorg]&lt;/code&gt; section, replace only the executable parameter that points to the wrapper with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib/xorg/Xorg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep all other parameters in place. The example’s remaining line is &lt;code&gt;param=/usr/lib/xorg/Xorg&lt;/code&gt;; avoid adding or removing parameters based only on the example, since your actual configuration may differ.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restart the session manager to apply the configuration change.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart xrdp-sesman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can interrupt remote sessions, so reconnect after the restart completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify
&lt;/h2&gt;

&lt;p&gt;Reconnect to the remote desktop and select Xorg as the session type. Confirm that the graphical desktop opens and that you can use it normally. Check the actual remote desktop after login; a successful SSH test is not a substitute for verifying the graphical session.&lt;/p&gt;

&lt;p&gt;Any IP addresses, process IDs, or session numbers shown in examples are placeholders. Use the connection and session details that apply to your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Applying this change because an SSH test showed the denial, without first confirming that the failing xrdp backend uses the wrapper.&lt;/li&gt;
&lt;li&gt;Replacing or deleting more than the executable parameter in &lt;code&gt;[Xorg]&lt;/code&gt;. Preserve all other parameters in the section.&lt;/li&gt;
&lt;li&gt;Forgetting to restart &lt;code&gt;xrdp-sesman&lt;/code&gt; after editing the configuration.&lt;/li&gt;
&lt;li&gt;Treating the package status as proof that the graphical session works; reconnect and verify the desktop.&lt;/li&gt;
&lt;li&gt;Restarting without accounting for the possibility of interrupting remote sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a clean Ubuntu box to practice on, PowerVPS is a reasonable choice: &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;https://powervps.net/?from=32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch more fixes on Server Lab: &lt;a href="https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Rotate Logs on a Linux Server Without Losing Data</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:00:21 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/how-to-rotate-logs-on-a-linux-server-without-losing-data-40nf</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/how-to-rotate-logs-on-a-linux-server-without-losing-data-40nf</guid>
      <description>&lt;p&gt;A disk alert fired at 2 a.m. because one application log had grown to 38 GB—and the service kept writing to it after the file was renamed. Renaming a log is not the same as making a process start a new one. Here’s how to set up log rotation, check that your application reopens its logs, and avoid the common traps that can cost you data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What log rotation does (and doesn’t do)
&lt;/h2&gt;

&lt;p&gt;Log rotation typically renames a full log, creates a fresh file at the original path, and eventually compresses or removes older files. On most Linux distributions, &lt;code&gt;logrotate&lt;/code&gt; handles this work.&lt;/p&gt;

&lt;p&gt;But rotation alone doesn’t tell an application to stop writing to the old file. A process usually keeps an open file descriptor, which points to the original file even after it’s renamed. The result: the new log stays empty while the renamed file keeps growing.&lt;/p&gt;

&lt;p&gt;The safe approach is to rotate the file &lt;strong&gt;and&lt;/strong&gt; tell the application to reopen its logs—or use &lt;code&gt;copytruncate&lt;/code&gt; as a fallback when the application cannot do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check what’s already installed
&lt;/h2&gt;

&lt;p&gt;Before adding a configuration, check whether logrotate is present and how it’s scheduled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;logrotate &lt;span class="nt"&gt;--version&lt;/span&gt;
systemctl list-timers | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; logrotate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Debian or Ubuntu, install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;logrotate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Fedora or RHEL-compatible systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;logrotate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many systems already include a daily timer or cron job. Avoid adding a second schedule without checking first; two rotation runs can make troubleshooting confusing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure a log that the application can reopen
&lt;/h2&gt;

&lt;p&gt;Suppose an application writes to &lt;code&gt;/var/log/myapp/app.log&lt;/code&gt;, runs as &lt;code&gt;myapp&lt;/code&gt;, and has a systemd unit called &lt;code&gt;myapp.service&lt;/code&gt;. Create a dedicated config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/logrotate.d/myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;myapp&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; {
    &lt;span class="n"&gt;daily&lt;/span&gt;
    &lt;span class="n"&gt;rotate&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
    &lt;span class="n"&gt;missingok&lt;/span&gt;
    &lt;span class="n"&gt;notifempty&lt;/span&gt;
    &lt;span class="n"&gt;compress&lt;/span&gt;
    &lt;span class="n"&gt;delaycompress&lt;/span&gt;
    &lt;span class="n"&gt;dateext&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="n"&gt;adm&lt;/span&gt;
    &lt;span class="n"&gt;su&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="n"&gt;adm&lt;/span&gt;

    &lt;span class="n"&gt;postrotate&lt;/span&gt;
        /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;systemctl&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt; --&lt;span class="n"&gt;signal&lt;/span&gt;=&lt;span class="n"&gt;HUP&lt;/span&gt; --&lt;span class="n"&gt;kill&lt;/span&gt;-&lt;span class="n"&gt;whom&lt;/span&gt;=&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;.&lt;span class="n"&gt;service&lt;/span&gt;
    &lt;span class="n"&gt;endscript&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust the path, user, group, and unit name for your setup. The &lt;code&gt;myapp&lt;/code&gt; process must handle &lt;code&gt;SIGHUP&lt;/code&gt; by closing and reopening its log file. Check its documentation before relying on that behavior. If it doesn’t support reopening logs, use the fallback below.&lt;/p&gt;

&lt;p&gt;Here’s what the directives do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;daily&lt;/code&gt; checks the log once a day; rotation happens only if it isn’t empty.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rotate 14&lt;/code&gt; keeps 14 rotated files. With daily rotation, that’s roughly two weeks, but actual retention depends on when the file was last rotated.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compress&lt;/code&gt; gzips older logs; &lt;code&gt;delaycompress&lt;/code&gt; leaves the newest rotated file uncompressed until the next rotation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dateext&lt;/code&gt; gives rotated files a date suffix, which makes them easier to identify.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create 0640 myapp adm&lt;/code&gt; creates a replacement file with the specified permissions and ownership.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;su myapp adm&lt;/code&gt; runs file operations as that user and group. This is useful for logs in directories owned by an application account. Check that the logrotate version and permissions on your system support this arrangement.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postrotate&lt;/code&gt; runs the command that asks the service to reopen its log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A practical tip:&lt;/strong&gt; test the signal handling before trusting the schedule. Check that the application logs to the newly created &lt;code&gt;app.log&lt;/code&gt; after a manual rotation. A successful &lt;code&gt;systemctl&lt;/code&gt; command only proves the signal was sent; it doesn’t prove the application acted on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test without forcing a rotation
&lt;/h2&gt;

&lt;p&gt;First, check the config for errors without changing files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;logrotate &lt;span class="nt"&gt;--debug&lt;/span&gt; /etc/logrotate.d/myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debug mode doesn’t rotate logs or update the state file. To test the real rotation, back up a non-production log or use a test service, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;logrotate &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt; /etc/logrotate.d/myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--force&lt;/code&gt; rotates regardless of the schedule, so don’t use it casually on production logs. It can also make the next scheduled run behave differently because logrotate tracks when files were last rotated.&lt;/p&gt;

&lt;p&gt;After a test, inspect the files and ownership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /var/log/myapp/
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 20 /var/log/myapp/app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a line of application activity and confirm it appears in the new &lt;code&gt;app.log&lt;/code&gt;, not just in a rotated file. Then check the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status myapp.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When the application cannot reopen its log
&lt;/h2&gt;

&lt;p&gt;If the application has no supported reopen mechanism, &lt;code&gt;copytruncate&lt;/code&gt; may be the only practical option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;myapp&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; {
    &lt;span class="n"&gt;daily&lt;/span&gt;
    &lt;span class="n"&gt;rotate&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
    &lt;span class="n"&gt;missingok&lt;/span&gt;
    &lt;span class="n"&gt;notifempty&lt;/span&gt;
    &lt;span class="n"&gt;compress&lt;/span&gt;
    &lt;span class="n"&gt;copytruncate&lt;/span&gt;
    &lt;span class="n"&gt;su&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="n"&gt;adm&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;copytruncate&lt;/code&gt;, logrotate copies the current file and then truncates the original in place. The application continues writing through its existing file descriptor.&lt;/p&gt;

&lt;p&gt;There’s a trade-off: a small amount of log data can be lost if the application writes between the copy and the truncate. For high-volume or audit-critical logs, prefer a supported reopen signal or another logging setup that streams to syslog or journald. Don’t treat &lt;code&gt;copytruncate&lt;/code&gt; as perfectly lossless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set retention based on disk, not habit
&lt;/h2&gt;

&lt;p&gt;Fourteen daily files can be reasonable for a small service, but log volume matters more than the number of files. If an app writes 500 MB a day, two weeks of uncompressed logs can use several gigabytes. Compression helps, but the savings depend on the log contents.&lt;/p&gt;

&lt;p&gt;Check actual usage before choosing a retention period:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /var/log/myapp
&lt;span class="nb"&gt;sudo du&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /var/log/myapp | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check whether your application already performs its own rotation. Two independent rotation mechanisms acting on the same file can cause missing logs or unexpected filenames.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Another tip from practice:&lt;/strong&gt; monitor free disk space separately from log rotation. Rotation can fail because of a full filesystem—the very situation it’s meant to help with. Alert before the disk is nearly full, and consider whether logs should live on a separate filesystem on busy servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you’re choosing a server
&lt;/h2&gt;

&lt;p&gt;For a standard Linux VM that runs an application and its logs, &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;PowerVPS&lt;/a&gt; is one option to evaluate. For GPU-heavy workloads, &lt;a href="https://cloud.vast.ai/?ref_id=588745" rel="noopener noreferrer"&gt;Vast.ai&lt;/a&gt; offers GPU rental; compare availability, storage, and pricing for the specific workload before committing. Neither changes the rotation basics: size retention for the disk you have, and make sure the application can reopen its logs.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://serverrental.store" rel="noopener noreferrer"&gt;Server Rental Guide&lt;/a&gt; is also a useful resource when comparing hosting options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A reliable rotation setup needs more than a schedule: configure retention and permissions, then make sure the application reopens its log after rotation. Test with &lt;code&gt;--debug&lt;/code&gt; first, and use &lt;code&gt;--force&lt;/code&gt; only when you’re prepared for an actual rotation. When reopen signals aren’t supported, &lt;code&gt;copytruncate&lt;/code&gt; is a workable fallback—but it has a small data-loss window. Finally, monitor disk space; logrotate is useful, not a substitute for an alert.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>From Queue to Cleanup: A One-Command Vast.ai Batch Pipeline</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Fri, 23 Oct 2026 08:00:02 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/from-queue-to-cleanup-a-one-command-vastai-batch-pipeline-9n7</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/from-queue-to-cleanup-a-one-command-vastai-batch-pipeline-9n7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We use one local command to search for a GPU, start an instance, wait for SSH, upload a queue, run its processing script, copy results home, verify the transfer, and destroy the instance. A detached watchdog sets a spending deadline in case the controller crashes. The script below is a starting point: supply the model image, an offer query, and a queue-specific &lt;code&gt;process.sh&lt;/code&gt;. Keep a separate backup of valuable results before destroying the instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we put the steps in one command
&lt;/h2&gt;

&lt;p&gt;We are a small content team that rents GPUs on Vast.ai for AI video work. Earlier in this series, we covered individual model runs and the setup they needed. The recurring problem was everything around inference: finding a suitable machine, pulling weights, moving inputs and outputs, and remembering to clean up.&lt;/p&gt;

&lt;p&gt;Setup can dominate a short run. In one LatentSync self-test, the queued batch took 15.9 minutes, including 11 minutes of setup. Running more jobs in one session made that setup easier to justify. It also made cleanup more consequential: a stopped instance still billed for storage.&lt;/p&gt;

&lt;p&gt;Our pipeline has two rules. Results must pass a local count and size check before normal cleanup. Separately, a watchdog must be able to destroy the instance even if the controller disappears. Those rules can conflict at the deadline, so the deadline is an emergency cap, not a promise that unfinished work will be saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract for a batch
&lt;/h2&gt;

&lt;p&gt;The local queue is a directory containing inputs and an executable &lt;code&gt;process.sh&lt;/code&gt;. The script in this article copies that directory to &lt;code&gt;/workspace/queue&lt;/code&gt; and runs its &lt;code&gt;process.sh&lt;/code&gt; on the rented machine. That script must put every deliverable under &lt;code&gt;/workspace/results&lt;/code&gt;. It is where model-specific commands belong.&lt;/p&gt;

&lt;p&gt;Prepare an image with the runtime and repository your workload needs. We built a public Docker image on GHCR using GitHub Actions, then used an on-start script to download only the selected weights. For example, &lt;code&gt;DL_GROUPS=h3&lt;/code&gt; selected the H3 weights, and the Vast on-start command was &lt;code&gt;bash /workspace/bootstrap/onstart.sh&lt;/code&gt;. The image build took about 11 minutes in our setup.&lt;/p&gt;

&lt;p&gt;The pipeline deliberately treats &lt;code&gt;process.sh&lt;/code&gt; as a black box. An image-to-video queue and a lip-sync queue need different arguments, but they need the same rental, transfer, verification, and cleanup sequence.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What the controller checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;Offer is rentable, reliable, has enough disk, and has no inbound traffic charge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;td&gt;Instance starts with the selected image, disk, label, and on-start command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect&lt;/td&gt;
&lt;td&gt;SSH accepts a command before any upload begins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;process.sh&lt;/code&gt; exits successfully and writes to &lt;code&gt;/workspace/results&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy&lt;/td&gt;
&lt;td&gt;Results arrive in the local output directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify&lt;/td&gt;
&lt;td&gt;Local file count and total bytes match the remote directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destroy&lt;/td&gt;
&lt;td&gt;The instance is destroyed and the current instance list confirms it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choose the offer before starting the clock
&lt;/h2&gt;

&lt;p&gt;The CLI search we used is &lt;code&gt;vastai search offers '&amp;lt;query&amp;gt;' -o 'dph+' --raw&lt;/code&gt;. Filtering matters more than selecting the first low hourly price. We look for reliability of at least &lt;code&gt;0.97&lt;/code&gt;, sufficient disk, and &lt;code&gt;inet_down_cost&lt;/code&gt; of zero. We also check &lt;code&gt;cuda_max_good&lt;/code&gt; or &lt;code&gt;driver_version&lt;/code&gt; against the image. One A100 we found in Sweden had driver 535, which did not fit the software we needed.&lt;/p&gt;

&lt;p&gt;Provision disk generously when pulling large weights. We use at least 150 GB for those sessions; 40 GB is suitable only for smaller ones. On an earlier RTX PRO 6000 Blackwell test, inbound traffic for 245 GB of model weights added $0.64. The GPU portion was $1.47, but the total was $2.18. That is why the query in the example excludes inbound traffic charges.&lt;/p&gt;

&lt;p&gt;Read the offer returned by search before supplying the hourly rate to the controller. Its cost deadline is calculated from &lt;em&gt;your stated rate&lt;/em&gt;, so a wrong rate makes the deadline wrong too. It is a guardrail, not accounting software: it cannot predict every charge. If comparing this approach with another vendor’s API, check that vendor’s current pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  A controller and a detached watchdog
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;batch.py&lt;/code&gt;. It uses the verified Vast CLI commands for offer search, creation, transfer, and destruction. The CLI should already be configured with your key. Set &lt;code&gt;VAST_API_KEY&lt;/code&gt; in the environment for the separate v1 instance-list check; the script never prints it.&lt;/p&gt;

&lt;p&gt;The watchdog is another invocation of the same file, started as a detached process. It waits until the deadline, tries destruction up to three times, then checks the v1 instance list. On a normal run, the controller destroys the instance after verification. A later watchdog check will find nothing to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlsplit&lt;/span&gt;


&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://console.vast.ai/api/v1/instances/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vastai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;listed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VAST_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;listed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;destroy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;listed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ssh_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssh-url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssh://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ssh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unrecognized ssh-url output: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watchdog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Destroy was not confirmed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;process.sh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Queue needs a process.sh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterdir&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Output directory must be empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap_usd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hourly_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cap and hourly rate must be positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;offers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dph+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;offer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;offers&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rentable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reliability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.97&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disk_space&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inet_down_cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--onstart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onstart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-e DL_GROUPS=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dl_groups&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--ssh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--direct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_contract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap_usd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hourly_rate&lt;/span&gt;

    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--watchdog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="n"&gt;start_new_session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Instance &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;; deadline set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ssh_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SSH did not become ready before deadline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mkdir -p /workspace/queue /workspace/results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;copy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:/workspace/queue/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash /workspace/queue/process.sh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;vast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;copy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:/workspace/results/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3 -c &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;import pathlib,json; p=pathlib.Path(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;/workspace/results&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;); f=[x for x in p.rglob(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;if x.is_file()]; print(json.dumps([len(f), &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sum(x.stat().st_size for x in f)]))&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transfer mismatch: remote=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, local=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verified &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; files, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Results copied, but destroy was not confirmed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Instance destroyed and absence confirmed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--watchdog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video-batch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--onstart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;onstart.sh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--dl-groups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--cap-usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--hourly-rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watchdog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;watchdog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watchdog&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watchdog&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script expects JSON output with an offer list and a creation response containing &lt;code&gt;new_contract&lt;/code&gt;. Check those shapes against your installed CLI before relying on an unattended run. It also assumes the rented image has &lt;code&gt;python3&lt;/code&gt; for the remote count and size check. The watchdog inherits &lt;code&gt;VAST_API_KEY&lt;/code&gt;, so keep that variable available until cleanup is confirmed.&lt;/p&gt;

&lt;p&gt;A call can then be reduced to one command. Replace the image and offer query with values you inspected, and set &lt;code&gt;--hourly-rate&lt;/code&gt; to the rate you used when calculating the cap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 batch.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--queue&lt;/span&gt; ./queue &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; ./results &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'reliability &amp;gt;= 0.97 num_gpus = 1 disk_space &amp;gt;= 150 inet_down_cost = 0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt; YOUR_IMAGE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; h3-batch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--onstart&lt;/span&gt; onstart.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dl-groups&lt;/span&gt; h3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; 150 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-usd&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--hourly-rate&lt;/span&gt; YOUR_OFFER_HOURLY_RATE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a first run, use a small queue and watch the output. The command starts the rental immediately; the cap begins at creation, so weight downloads and SSH startup consume part of it. Increase the cap only after allowing for setup and the work you have actually queued.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify, back up, then destroy
&lt;/h2&gt;

&lt;p&gt;The local comparison catches incomplete transfers by checking file count and total bytes. It does not prove that two same-sized files have identical contents. For valuable output, keep a separate backup before destruction. In our workflow, we copied results to a cloud remote with rclone and checked its file count and size; one backup check matched 5,342 files and 1.379 GiB against local storage.&lt;/p&gt;

&lt;p&gt;We learned the cleanup rule the expensive way. A stopped instance with a 90 GB disk kept billing about $0.0167 per hour, or about $0.40 per day. We left one stopped for days, the balance went negative, the stopped instance could not be restarted, and we never downloaded the trained result. Now “finished” means results are copied, backed up, and the instance is &lt;strong&gt;destroyed&lt;/strong&gt;. We verify absence through the v1 instances endpoint because an older v0 listing returned an error that looked like “0 instances.”&lt;/p&gt;

&lt;p&gt;The code sends &lt;code&gt;y&lt;/code&gt; to &lt;code&gt;vastai destroy instance&lt;/code&gt;, matching the CLI prompt we encountered. Newer CLI versions also offer &lt;code&gt;-y&lt;/code&gt;. If processing fails, the controller raises an error and leaves recovery time until the watchdog deadline; inspect the instance promptly. Once that deadline arrives, the watchdog prioritizes the cap.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What it cost us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On a Vast A100 SXM4 80GB in Czechia, MiniMax H3 image-to-video took about 86 seconds per five-second clip. The GPU rate was $1.19 per hour, about $1.25 with disk, and the run cost about $0.03 per clip. A session producing 82 hero clips cost $3.59 over about three hours, including setup; 72 GB of weights downloaded in roughly eight minutes.&lt;/p&gt;

&lt;p&gt;Our earlier RTX PRO 6000 Blackwell test ran for 56 minutes and cost $2.18: $1.47 for GPU time and $0.64 for inbound traffic on 245 GB of weights. A LatentSync 1.6 run on a Vast RTX 3090 at $0.27 per hour with free inbound took 238 seconds for one clip, about $0.09. These are our runs, not promises about future offers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A batch script does not make rented GPU work effortless. It gives each session a repeatable beginning and a definite end. We still inspect the offer, match its driver to the image, size the disk for the weights, and keep model commands inside the queue’s processing script. The payoff is that setup serves a batch, outputs are checked before cleanup, and a controller failure has a deadline.&lt;/p&gt;

&lt;p&gt;If you want to try the same approach, &lt;a href="https://cloud.vast.ai/?ref_id=588745" rel="noopener noreferrer"&gt;this is our Vast.ai referral link&lt;/a&gt;. Start with a small queue, confirm that your CLI and image match the skeleton, and make destruction part of the successful path.&lt;/p&gt;

</description>
      <category>gpu</category>
      <category>ai</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>Caching Strategies for Dynamic Websites on a Budget VPS</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:00:43 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/caching-strategies-for-dynamic-websites-on-a-budget-vps-36a</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/caching-strategies-for-dynamic-websites-on-a-budget-vps-36a</guid>
      <description>&lt;p&gt;A page that normally loaded in 180 ms took nearly four seconds during a small traffic spike—not because the VPS ran out of CPU, but because every request repeated the same database work. A short cache at the web server would have absorbed most of that load. This walkthrough covers a practical caching stack for a budget VPS, including what to cache, how to avoid leaking personalized pages, and how to check that the cache is actually working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the cheapest wins
&lt;/h2&gt;

&lt;p&gt;Caching is not one switch. It’s a few layers, each useful for different kinds of content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser caching&lt;/strong&gt; reduces repeat downloads of static files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse-proxy caching&lt;/strong&gt; lets Nginx serve eligible responses without calling your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application or data caching&lt;/strong&gt; avoids repeating expensive database queries or computations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with the layer that matches the bottleneck. If a page is slow because it runs a costly query, caching the CSS won’t fix it. If the page is fast but users repeatedly download a large bundle, browser caching is an easy win.&lt;/p&gt;

&lt;p&gt;Before changing anything, get a baseline. Check a representative page a few times and note its response time, status code, and cache-related headers. Then check your application and database metrics if you have them. A cache that serves the wrong content is worse than a slow page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache static assets in the browser
&lt;/h2&gt;

&lt;p&gt;Give versioned assets—such as &lt;code&gt;app.a1b2c3.js&lt;/code&gt;—a long browser lifetime. The filename changes when the content changes, so returning visitors can reuse the old download safely until they receive the new filename.&lt;/p&gt;

&lt;p&gt;For Nginx, a typical rule looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(css|js|png|jpg|jpeg|gif|svg|webp|woff2)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;30d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;max-age=2592000,&lt;/span&gt; &lt;span class="s"&gt;immutable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;immutable&lt;/code&gt; only if your build process changes the asset URL when its contents change. If you overwrite &lt;code&gt;app.js&lt;/code&gt; in place, a 30-day cache can leave users running stale code. For HTML, use a shorter lifetime or revalidate; pages often need to reflect changes sooner than static assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a small Nginx microcache
&lt;/h2&gt;

&lt;p&gt;For public pages that are expensive to render but can tolerate a few seconds of staleness, Nginx’s proxy cache is a good fit. A 5–15 second cache can collapse a burst of identical requests into one application request without making a page feel stale to most visitors.&lt;/p&gt;

&lt;p&gt;The example below assumes Nginx proxies requests to an application listening on &lt;code&gt;127.0.0.1:3000&lt;/code&gt;. Put the &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;proxy_cache_path&lt;/code&gt; directives in the &lt;code&gt;http&lt;/code&gt; block, not inside a &lt;code&gt;server&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_cache_path&lt;/span&gt; &lt;span class="n"&gt;/var/cache/nginx&lt;/span&gt;
        &lt;span class="s"&gt;levels=1:2&lt;/span&gt;
        &lt;span class="s"&gt;keys_zone=site_cache:10m&lt;/span&gt;
        &lt;span class="s"&gt;max_size=256m&lt;/span&gt;
        &lt;span class="s"&gt;inactive=10m&lt;/span&gt;
        &lt;span class="s"&gt;use_temp_path=off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$http_cookie&lt;/span&gt; &lt;span class="nv"&gt;$skip_cookie_cache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;default&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;""&lt;/span&gt;      &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$http_authorization&lt;/span&gt; &lt;span class="nv"&gt;$skip_auth_cache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;default&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;""&lt;/span&gt;      &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kn"&gt;proxy_cache&lt;/span&gt; &lt;span class="s"&gt;site_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_key&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scheme$request_method$host$request_uri&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_methods&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt; &lt;span class="s"&gt;HEAD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_valid&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="s"&gt;1s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_cache_lock&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kn"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$skip_cookie_cache&lt;/span&gt; &lt;span class="nv"&gt;$skip_auth_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_no_cache&lt;/span&gt;     &lt;span class="nv"&gt;$skip_cookie_cache&lt;/span&gt; &lt;span class="nv"&gt;$skip_auth_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Cache&lt;/span&gt; &lt;span class="nv"&gt;$upstream_cache_status&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This conservative setup bypasses caching whenever a request has a cookie or an &lt;code&gt;Authorization&lt;/code&gt; header. That’s safer than trying to guess which cookies imply a logged-in user. If your site sets an anonymous analytics cookie on every visit, this rule may bypass more requests than necessary; refine it only after confirming which cookies affect page content.&lt;/p&gt;

&lt;p&gt;Check the configuration and reload Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then request a public page twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSI&lt;/span&gt; https://example.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'HTTP/|x-cache|cache-control'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request will often show &lt;code&gt;MISS&lt;/code&gt;; a later request may show &lt;code&gt;HIT&lt;/code&gt;. A response can still be uncacheable if the application sends headers such as &lt;code&gt;Cache-Control: private&lt;/code&gt; or &lt;code&gt;no-store&lt;/code&gt;. That’s usually intentional—don’t override those headers until you understand why they’re there.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;proxy_cache_lock on&lt;/code&gt; is a useful, non-obvious safeguard: when a popular item expires, it helps prevent many simultaneous requests from all regenerating it at once. Also, &lt;code&gt;keys_zone=site_cache:10m&lt;/code&gt; is Nginx’s in-memory index, not the maximum disk cache size; &lt;code&gt;max_size=256m&lt;/code&gt; sets the disk limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep personalized and changing content out
&lt;/h2&gt;

&lt;p&gt;A page containing a user’s name, cart, account balance, or private dashboard should not be served from a shared cache. Bypassing on cookies and authorization headers is a safe starting point, but the application should also send appropriate &lt;code&gt;Cache-Control&lt;/code&gt; headers for private responses.&lt;/p&gt;

&lt;p&gt;Be equally deliberate with query parameters. Nginx’s default cache key includes the request URI, including its query string. That’s often right for searches or filters, but it can create many nearly identical cache entries for tracking parameters such as &lt;code&gt;utm_source&lt;/code&gt;. If you normalize URLs, confirm first that the removed parameters never change the response.&lt;/p&gt;

&lt;p&gt;For content that changes immediately—inventory, account settings, or breaking news—use no cache or a very short lifetime. For articles and public landing pages, a few seconds may be enough to smooth traffic spikes while keeping updates reasonably fresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use application caching for expensive work
&lt;/h2&gt;

&lt;p&gt;A reverse proxy can cache whole responses; it can’t efficiently cache every expensive operation inside your application. If one database query dominates page generation, cache that result in the application using a key that includes every input that affects the answer.&lt;/p&gt;

&lt;p&gt;For example, a product list might be cached by category and page number, with a short expiration. Avoid a single key such as &lt;code&gt;products&lt;/code&gt; if different users, locales, currencies, or filters receive different results.&lt;/p&gt;

&lt;p&gt;A Redis server can be useful for shared application data, but it adds another service to monitor and size. On a small VPS, start with a bounded cache and watch memory use. An unbounded in-process cache can quietly consume the RAM your database or application needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fit the cache to the VPS
&lt;/h2&gt;

&lt;p&gt;Disk cache is useful when RAM is limited, but it is not free: the cache competes with logs, uploads, and database files for storage. Start small—perhaps a 128–256 MB Nginx cache—and inspect disk usage before increasing it. For a single-site VPS, a short microcache and browser caching are often better starting points than adding multiple services.&lt;/p&gt;

&lt;p&gt;When choosing a VPS, compare memory, disk type, bandwidth, and support as well as the monthly price. PowerVPS is one option worth comparing for a small web deployment. For a site that adds AI-generated features, Vast.ai can be useful for GPU rental, but a GPU usually won’t improve ordinary page caching; keep that workload separate unless you have a specific reason to colocate it. The &lt;a href="https://serverrental.store" rel="noopener noreferrer"&gt;Server Rental Guide&lt;/a&gt; is also a useful resource when comparing hosting and rental options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make changes safely
&lt;/h2&gt;

&lt;p&gt;A practical rollout looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Record baseline response times and identify one slow, public page.&lt;/li&gt;
&lt;li&gt;Add browser caching for versioned static assets.&lt;/li&gt;
&lt;li&gt;Enable a short reverse-proxy cache for eligible &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; responses.&lt;/li&gt;
&lt;li&gt;Confirm repeated requests show cache hits and personalized requests bypass the cache.&lt;/li&gt;
&lt;li&gt;Watch application load, disk usage, and error rates during real traffic.&lt;/li&gt;
&lt;li&gt;Adjust the cache lifetime only after checking freshness requirements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One more tip from practice: test as both an anonymous visitor and a logged-in user. A page that looks correct in your own browser may be getting a cache hit from a previous anonymous request—or the reverse. Also test after a content update. A cache that works perfectly until an editor publishes an urgent correction still needs an invalidation plan; for a small site, a short TTL may be the simplest plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A budget VPS can handle more traffic without a complicated cache stack. Cache versioned static assets in browsers, use a short Nginx microcache for safe public pages, and cache expensive application data where it makes sense. Keep personalized responses out of shared caches, verify hits and bypasses with real requests, and size the cache against your actual disk and memory budget. Start small, measure the result, and expand only where the numbers point.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Before You Destroy a Rented GPU, Prove Your Results Are Safe</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:00:02 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/before-you-destroy-a-rented-gpu-prove-your-results-are-safe-54p1</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/before-you-destroy-a-rented-gpu-prove-your-results-are-safe-54p1</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; We rent GPUs on Vast.ai for AI video work. Our rule now is simple: copy every result off the instance, make a second copy in cloud storage, compare file counts and byte totals, then verify content before destroying the instance. A stopped instance still bills for storage. We learned that after leaving one stopped for days; the balance went negative, we could not restart it, and we never downloaded the trained result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why “the job finished” is not the finish line
&lt;/h2&gt;

&lt;p&gt;A render finishing on a rented GPU means the files exist on that machine. It does not mean we have them.&lt;/p&gt;

&lt;p&gt;This distinction matters when a session produces many clips. Our MiniMax H3 session produced 82 hero clips. The GPU work was useful only if those clips made it back to our workspace. The same applies to model weights, training outputs, logs, and any intermediate files we might need to diagnose a failed run.&lt;/p&gt;

&lt;p&gt;We once stopped an instance while a trained result was still on its disk. Storage continued billing at about $0.0167 per hour, or about $0.40 per day for a 90 GB disk. We left it stopped for days. The balance went negative, a stopped instance could not be started with that balance, and the result was never downloaded.&lt;/p&gt;

&lt;p&gt;That mistake changed our shutdown sequence. “Stop” is not our archival step. We copy, verify, back up, and then &lt;strong&gt;destroy&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every session its own output directory
&lt;/h2&gt;

&lt;p&gt;Before starting a batch, we choose one directory on the rented machine for final results. On the receiving machine, we give that session its own local directory and its own cloud backup prefix.&lt;/p&gt;

&lt;p&gt;A dedicated prefix makes verification much easier. If an old file is already in the cloud destination, a file count might look reassuring while hiding a missing new file. It also lets us tell exactly which session a backup belongs to.&lt;/p&gt;

&lt;p&gt;For a new Vast.ai account, &lt;a href="https://cloud.vast.ai/?ref_id=588745" rel="noopener noreferrer"&gt;this is our referral link&lt;/a&gt;. Check current offer details before renting: disk capacity and inbound traffic charges can change the cost of a session. We have seen inbound traffic billed separately when downloading large model weights.&lt;/p&gt;

&lt;p&gt;Keep the output directory focused on files you intend to retain. Temporary caches and downloaded weights belong elsewhere unless you explicitly want to archive them. For large weights, we provision disk generously; our H3 setup downloaded 72 GB of weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy results back with rsync
&lt;/h2&gt;

&lt;p&gt;We use an SSH connection to pull results to a local machine. &lt;code&gt;vastai ssh-url ID&lt;/code&gt; provides the connection details for an instance. Put the host, port, and user from that connection into environment variables, then run the transfer from the receiving machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-instance-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSH_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSH_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-instance-host"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSH_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-instance-ssh-port"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LOCAL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/results/session-a"&lt;/span&gt;

vastai ssh-url &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTANCE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

rsync &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--partial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ssh -p &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SSH_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SSH_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SSH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:/workspace/outputs/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trailing slash on &lt;code&gt;/workspace/outputs/&lt;/code&gt; means “copy the contents of this directory.” The local directory is a session-specific destination. &lt;code&gt;--partial&lt;/code&gt; helps when a connection drops during a large transfer, and rerunning &lt;code&gt;rsync&lt;/code&gt; is a practical way to resume the copy.&lt;/p&gt;

&lt;p&gt;The same general rule applies if you use &lt;code&gt;vastai copy SRC DST&lt;/code&gt;: confirm the destination contains the expected files before treating the transfer as complete. A successful command is useful evidence, but we still check the files themselves.&lt;/p&gt;

&lt;p&gt;Do not destroy the instance just because &lt;code&gt;rsync&lt;/code&gt; exited successfully. The local copy is only the first checkpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a SHA-256 manifest
&lt;/h2&gt;

&lt;p&gt;A manifest records the expected digest of every file in the local results directory. We keep it outside that directory so it does not accidentally become part of the result set.&lt;/p&gt;

&lt;p&gt;The script below walks regular files, rejects symbolic links, and writes paths relative to the session directory. It also prints the file count and total bytes. It rejects filenames containing newlines because they make a plain text checksum manifest harder to inspect safely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# save as make_manifest.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing results directory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_symlink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Results contain a symbolic link; inspect it first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;relative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;relative&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unsupported filename: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;relative&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;relative&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;total_bytes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_bytes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, run it with &lt;code&gt;python3 make_manifest.py "$LOCAL_DIR" "$PWD/SHA256SUMS.session-a"&lt;/code&gt;. Save the manifest alongside your local backup records. If a file changes later, its digest changes too.&lt;/p&gt;

&lt;p&gt;A manifest made after the local transfer cannot prove what existed on the rented machine before the transfer. It gives us a clear record of what we received and a basis for checking later copies. If the source application reports an expected output count, compare that count with the local result as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back up to cloud storage and compare the copies
&lt;/h2&gt;

&lt;p&gt;We use &lt;code&gt;rclone&lt;/code&gt; to copy the local results to a cloud remote, then upload the manifest. The remote path below should be a fresh prefix for this session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;REMOTE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-remote:gpu-results/session-a"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MANIFEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/SHA256SUMS.session-a"&lt;/span&gt;

python3 make_manifest.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MANIFEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

rclone copy &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
rclone copyto &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MANIFEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/SHA256SUMS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our verification has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compare the names and sizes of the local result files with a recursive listing of the cloud prefix.&lt;/li&gt;
&lt;li&gt;Compare file contents, downloading remote data for the check where needed.&lt;/li&gt;
&lt;li&gt;Download the cloud copy of the manifest and compare it with the local manifest.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a compact pre-destroy check to run after the upload. It assumes &lt;code&gt;LOCAL_DIR&lt;/code&gt;, &lt;code&gt;REMOTE_DIR&lt;/code&gt;, and &lt;code&gt;MANIFEST&lt;/code&gt; are set as above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCAL_DIR&lt;/span&gt;:?Set&lt;span class="p"&gt; LOCAL_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_DIR&lt;/span&gt;:?Set&lt;span class="p"&gt; REMOTE_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MANIFEST&lt;/span&gt;:?Set&lt;span class="p"&gt; MANIFEST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;listing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;remote_manifest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'rm -f "$listing" "$remote_manifest"'&lt;/span&gt; EXIT

rclone lsjson &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="nt"&gt;--files-only&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$listing&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$listing&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json
import pathlib
import sys

root = pathlib.Path(sys.argv[1])
listing = json.loads(pathlib.Path(sys.argv[2]).read_text())

local = {
    path.relative_to(root).as_posix(): path.stat().st_size
    for path in root.rglob("*")
    if path.is_file()
}

remote = {
    item["Path"]: item["Size"]
    for item in listing
    if item["Path"] != "SHA256SUMS"
}

if local != remote:
    missing = sorted(local.keys() - remote.keys())
    extra = sorted(remote.keys() - local.keys())
    changed = sorted(
        name for name in local.keys() &amp;amp; remote.keys()
        if local[name] != remote[name]
    )
    raise SystemExit(
        f"Backup mismatch: missing={missing}, extra={extra}, "
        f"different_size={changed}"
    )

print(f"Matched files={len(local)} bytes={sum(local.values())}")
&lt;/span&gt;&lt;span class="no"&gt;PY

&lt;/span&gt;rclone check &lt;span class="nt"&gt;--download&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; SHA256SUMS
rclone copyto &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/SHA256SUMS"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_manifest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
cmp &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MANIFEST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_manifest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"Local results and cloud backup verified."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name and size comparison catches missing, extra, and truncated files quickly. &lt;code&gt;rclone check --download&lt;/code&gt; adds a content comparison; it may transfer substantial data, so allow time for it. The downloaded manifest confirms that the cloud prefix also contains the exact checksum record we generated locally.&lt;/p&gt;

&lt;p&gt;In one backup verification, we matched 5,342 files and 1.379 GiB between local storage and the cloud remote. Those were the checks we wanted to see &lt;em&gt;before&lt;/em&gt; removing the rented disk.&lt;/p&gt;

&lt;p&gt;Our pre-destroy checklist, in the order we run it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Evidence we want&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;rsync&lt;/code&gt; from the instance&lt;/td&gt;
&lt;td&gt;Exit code 0 and the expected files locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256 manifest&lt;/td&gt;
&lt;td&gt;File count and byte total recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;rclone copy&lt;/code&gt; to a fresh prefix&lt;/td&gt;
&lt;td&gt;Listing matches names and sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rclone check --download&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contents match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manifest compared in the cloud&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cmp&lt;/code&gt; reports no difference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The last check happens after destroy
&lt;/h2&gt;

&lt;p&gt;Once the local copy, cloud copy, and manifest checks pass, we destroy the instance. The CLI prompts for confirmation; this form supplies the answer for a scripted shutdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;y | vastai destroy instance &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTANCE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then verify that the instance is gone. An old v0 instances listing was deprecated and once returned an error that looked like “0 instances,” so we do not treat that response as proof. We check the v1 instances endpoint at &lt;code&gt;https://console.vast.ai/api/v1/instances/&lt;/code&gt; with an &lt;code&gt;Authorization: Bearer &amp;lt;key&amp;gt;&lt;/code&gt; header, keeping the key in an environment variable or a protected file rather than printing or hard-coding it.&lt;/p&gt;

&lt;p&gt;We also use a cost cap and a detached deadline watchdog during unattended sessions. The watchdog retries &lt;code&gt;destroy&lt;/code&gt; if the controller crashes. It is a guardrail for the rental meter, not a replacement for copying results promptly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What it cost us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our H3 batch on a Vast A100 SXM4 80GB in Czechia produced 82 hero clips during a roughly three-hour session for $3.59, including setup. A separate LatentSync 1.6 queued batch self-test took 15.9 minutes and cost $0.07; setup accounted for about 11 minutes. The painful cost was the stopped 90 GB disk: about $0.0167 per hour, or $0.40 per day, followed by a trained result we never recovered. These are our runs, not price guarantees.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Batching work makes GPU rental economical for us because setup takes a meaningful share of a short session. Safe shutdown is part of that economics. We treat the local copy, the cloud copy, and their verification as deliverables of the job. Only then is the instance finished.&lt;/p&gt;

</description>
      <category>gpu</category>
      <category>ai</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Fix colord Color Profile Authentication Popup in xRDP Ubuntu 24.04</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Mon, 19 Oct 2026 13:33:18 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/fix-colord-color-profile-authentication-popup-in-xrdp-ubuntu-2404-3io7</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/fix-colord-color-profile-authentication-popup-in-xrdp-ubuntu-2404-3io7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Create a &lt;code&gt;colord-users&lt;/code&gt; group for the RDP user and add a polkit rule that allows only the specified colord actions in active sessions.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/wDunqfnXVZw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;On Ubuntu 24.04 over xRDP, opening a color setting can show an authentication popup: “Authentication is required to create a color managed device.” It interrupts what should be a routine desktop task and asks for administrator authentication.&lt;/p&gt;

&lt;p&gt;This guide applies a scoped fix. It grants the RDP user permission for two color-management actions when their session is active. It does not grant sudo access or broadly remove authentication requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;The session matters. Polkit treats a remote session as non-local, and the default colord policy asks for administrator authentication. There is no stock rule covering remote sessions for these colord actions, so the prompt appears.&lt;/p&gt;

&lt;p&gt;The rule below checks three conditions: the requested action is one of two colord actions, the user belongs to a dedicated group, and the session is active. If all three conditions match, the action is allowed. Unrelated actions still follow their existing policies.&lt;/p&gt;

&lt;p&gt;The session ID, user name, and numeric IDs shown in the examples are placeholders. Use the session ID and RDP user that apply to your system; the sample output illustrates the expected format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check the current session ID:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$XDG_SESSION_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Inspect the session’s remote status and type. Replace &lt;code&gt;4&lt;/code&gt; with the session ID from the previous step if needed:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   loginctl show-session 4 &lt;span class="nt"&gt;-p&lt;/span&gt; Remote &lt;span class="nt"&gt;-p&lt;/span&gt; Type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Remote=yes
   Type=x11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Remote=yes&lt;/code&gt; confirms that this session is remote. The output’s session ID and session properties are examples; your values may differ.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the dedicated group:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd colord-users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add only the RDP user to that group. Replace &lt;code&gt;rdpuser&lt;/code&gt; with the account used to log in over RDP:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; colord-users rdpuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the account’s group membership:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;id &lt;/span&gt;rdpuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   uid=1002(rdpuser) gid=1002(rdpuser) groups=1002(rdpuser),1004(colord-users)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numeric IDs and account details in this sample are placeholders. Confirm that &lt;code&gt;colord-users&lt;/code&gt; appears in the groups list.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;/etc/polkit-1/rules.d/50-colord-xrdp.rules&lt;/code&gt; with exactly this content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   polkit.addRule&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;action, subject&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   var ok &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"org.freedesktop.color-manager.create-device"&lt;/span&gt;, &lt;span class="s2"&gt;"org.freedesktop.color-manager.create-profile"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ok.indexOf&lt;span class="o"&gt;(&lt;/span&gt;action.id&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; subject.isInGroup&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"colord-users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; subject.active&lt;span class="o"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;return &lt;/span&gt;polkit.Result.YES&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule allows the two listed colord actions for members of &lt;code&gt;colord-users&lt;/code&gt; only while their session is active.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log out of the desktop session and back in over RDP. The new group membership takes effect in a fresh login session, so reconnect before checking the permission.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to verify
&lt;/h2&gt;

&lt;p&gt;After logging in again, check the create-device action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;org.freedesktop.color-manager.create-device
pkcheck &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A result of &lt;code&gt;0&lt;/code&gt; means colord is allowed for this action in the current session.&lt;/p&gt;

&lt;p&gt;Then check an unrelated systemd action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;B&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;org.freedesktop.systemd1.manage-units
pkcheck &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$B&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A result of &lt;code&gt;1&lt;/code&gt; here means administrator authentication is still required for that unrelated action. The PID and result values shown are examples; the PID is supplied by &lt;code&gt;$$&lt;/code&gt; in your shell. Together, these checks show that the rule grants the intended colord permission without allowing systemd unit management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;Do not add every desktop user to &lt;code&gt;colord-users&lt;/code&gt;; the rule is intended for the RDP account that needs the color settings. Check the account name carefully when adding it to the group.&lt;/p&gt;

&lt;p&gt;Do not skip logging out and back in after changing group membership. An existing session may not reflect the new membership, which can make a correct rule appear ineffective.&lt;/p&gt;

&lt;p&gt;Keep the rule’s action list, group check, and active-session check intact. Removing a condition broadens who can use the exception or when it applies. If the create-device check does not return &lt;code&gt;0&lt;/code&gt;, confirm the session is active, the RDP account belongs to &lt;code&gt;colord-users&lt;/code&gt;, and the file contents match the rule above.&lt;/p&gt;

&lt;p&gt;If you want a clean Ubuntu box to practice on, PowerVPS is a reasonable choice: &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;https://powervps.net/?from=32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch more fixes on Server Lab: &lt;a href="https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running Stable Diffusion on a Rented GPU Server</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Mon, 19 Oct 2026 09:00:25 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/running-stable-diffusion-on-a-rented-gpu-server-1hh8</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/running-stable-diffusion-on-a-rented-gpu-server-1hh8</guid>
      <description>&lt;p&gt;A rented GPU that looked cheap on the hourly listing cost me more than expected: I left it running overnight after the first image finished. The model took minutes to load, but the idle machine kept billing for hours. This walkthrough covers choosing a GPU, setting up Stable Diffusion on Ubuntu, connecting securely, and avoiding that kind of surprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a server that fits the model
&lt;/h2&gt;

&lt;p&gt;For a first run, an NVIDIA GPU with 8 GB of VRAM is a reasonable target for Stable Diffusion 1.5. SDXL is more comfortable with 12 GB or more, though actual requirements depend on resolution, batch size, and optimizations. Check the GPU model and VRAM before renting; a listing’s “GPU memory” and its system RAM are different things.&lt;/p&gt;

&lt;p&gt;Disk space matters too. I’d start with at least 50 GB of persistent storage for the operating system, Python dependencies, model checkpoints, and generated images. More is useful if you plan to keep multiple models.&lt;/p&gt;

&lt;p&gt;Vast.ai is a good option I’ve used and tested for on-demand GPU rentals, with a range of hardware and prices. PowerVPS is another provider I’ve used and tested for VPS workloads; check its current offerings if you need a longer-lived server or supporting services, and confirm GPU availability before choosing a plan. In either case, compare the total cost, including storage and any charges that continue when the GPU is idle. The &lt;a href="https://serverrental.store" rel="noopener noreferrer"&gt;Server Rental Guide&lt;/a&gt; is a useful resource when comparing rental options and terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a clean Ubuntu server
&lt;/h2&gt;

&lt;p&gt;Choose an Ubuntu 22.04 image with NVIDIA drivers already installed if the provider offers one. That avoids spending your first session debugging driver setup. After connecting over SSH, confirm the GPU is visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the GPU model, driver version, and memory. If the command is missing or reports no devices, check the provider’s GPU image and driver instructions before installing anything else. Driver installation varies by image and provider.&lt;/p&gt;

&lt;p&gt;Install the basic tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git python3 python3-venv python3-pip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this example, use AUTOMATIC1111’s Stable Diffusion WebUI. Its first startup installs Python dependencies, so allow time for that, and keep the server’s disk persistent if you want to reuse the installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
&lt;span class="nb"&gt;cd &lt;/span&gt;stable-diffusion-webui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll need a model checkpoint that you’re allowed to use. Download it through the model host’s normal process, then copy it to the server or download it from the server if the host provides a direct link. Some model hosts require accepting a license or signing in. Put the &lt;code&gt;.safetensors&lt;/code&gt; file in the WebUI’s model directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; models/Stable-diffusion
&lt;span class="c"&gt;# Copy your checkpoint into models/Stable-diffusion/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, from your local computer, copy a file with &lt;code&gt;scp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp ./my-model.safetensors USER@HOST:stable-diffusion-webui/models/Stable-diffusion/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;USER&lt;/code&gt; and &lt;code&gt;HOST&lt;/code&gt; with the SSH login details supplied by your provider. This command assumes the clone is in the remote user’s home directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch it without exposing the UI publicly
&lt;/h2&gt;

&lt;p&gt;Start the WebUI so it listens on the server’s network interfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./webui.sh &lt;span class="nt"&gt;--listen&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 7860
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this process running in your SSH session for the first test. The UI does not require you to open port 7860 to the internet. Instead, keep the provider firewall or security group closed to inbound traffic on that port and create a tunnel from your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 7860:127.0.0.1:7860 USER@HOST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://127.0.0.1:7860&lt;/code&gt; in your local browser. The tunnel forwards your local port to the WebUI on the server. If port 7860 is already in use locally, choose another local port, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 7861:127.0.0.1:7860 USER@HOST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that case, open &lt;code&gt;http://127.0.0.1:7861&lt;/code&gt;. Avoid exposing an unauthenticated image-generation UI directly to the public internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a first image and check utilization
&lt;/h2&gt;

&lt;p&gt;Select the checkpoint in the UI, enter a prompt, and start with a modest configuration: 512×512 resolution, one image, and around 20 steps. Once that works, try larger resolutions or other settings.&lt;/p&gt;

&lt;p&gt;Watch GPU usage in a second SSH session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A GPU that appears underutilized may be waiting on model loading, disk access, or a setting that sends work to the CPU. Don’t assume a higher step count is always better; it increases generation time, and the visual difference depends on the prompt and sampler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two rental habits that save trouble
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keep models on persistent storage.&lt;/strong&gt; Many rental platforms distinguish between stopping a machine and deleting it. A stopped instance may retain its disk; deleting it may remove the model files too. Check the provider’s rules before you end a session, and copy anything important off the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat the hourly rate as only part of the cost.&lt;/strong&gt; A GPU with more VRAM may finish a job faster, but the cheapest listing is not automatically the cheapest run. For a short task, compare estimated runtime as well as hourly price. Also check whether persistent disks are billed while the GPU is stopped. The details vary by provider and rental type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shut down when you’re done
&lt;/h2&gt;

&lt;p&gt;Stop the WebUI with &lt;code&gt;Ctrl+C&lt;/code&gt; in its SSH session. Then stop or release the rented instance using the provider’s controls. Don’t assume closing SSH stops the server. On some platforms, the machine keeps running—and billing—until you explicitly stop or terminate it.&lt;/p&gt;

&lt;p&gt;If you expect to return later, choose the option that preserves the disk and confirm its storage charges. If you no longer need the files, back up the outputs and remove the instance according to the provider’s instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A rented GPU makes Stable Diffusion accessible without buying hardware, but the setup is only half the job: model storage, network access, and shutdown behavior all affect the experience and the bill. Start with a preconfigured Ubuntu GPU image, verify it with &lt;code&gt;nvidia-smi&lt;/code&gt;, use a model you’re licensed to run, and access the WebUI through an SSH tunnel. Most importantly, understand what happens to the server and its disk when you stop renting it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
    <item>
      <title>Fix No Space Left on Device on Ubuntu 24.04 Server</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Sun, 18 Oct 2026 13:31:14 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/fix-no-space-left-on-device-on-ubuntu-2404-server-3jj8</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/fix-no-space-left-on-device-on-ubuntu-2404-server-3jj8</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Find the known oversized leftover file on the full lab volume, remove it, then retry the write and check that free space has returned.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/RKiR3ol7cOA" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A write can fail with “No space left on device” even when you’re working on a separate lab volume rather than the server’s root disk. In this example, the lab volume is 64 MB. Filling it with a large file leaves no room for a normal write.&lt;/p&gt;

&lt;p&gt;The failed write is a symptom, not a diagnosis. First confirm which filesystem is full, then identify what is using its space. The commands below follow that path and remove only the known lab leftover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Filesystems track both data capacity and inodes. Bytes measure the space used by file contents; inodes track how many files and directories can be created. Either limit can prevent a new file from being written.&lt;/p&gt;

&lt;p&gt;Here, the volume is at 100% usage with zero bytes available. The large file accounts for 57 MB. The inode limit is fine: this is a bytes-full problem, not an inode shortage. The &lt;code&gt;lost+found&lt;/code&gt; permission message is separate; it means the current user cannot read that directory, and does not prevent &lt;code&gt;du&lt;/code&gt; from reporting the large file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce the failure by writing zeroes to the lab file. The command stops when the volume runs out of space:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/srv/lab/export.csv &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dd: error writing '/srv/lab/export.csv': No space left on device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Try a normal write so you can see whether the filesystem also rejects a small file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo test&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /srv/lab/new.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tee: /srv/lab/new.txt: No space left on device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check the capacity of the lab mount:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /srv/lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Filesystem Size Used Avail Use% Mounted on
/dev/loop0 59M 59M 0 100% /srv/lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Find the large entry under the lab directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /srv/lab/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;57M /srv/lab/export.csv
du: cannot read directory '/srv/lab/lost+found': Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Remove the known lab leftover:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; /srv/lab/export.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target is specific: &lt;code&gt;/srv/lab/export.csv&lt;/code&gt;. Check that you have identified the intended file before removing anything on a real server.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify
&lt;/h2&gt;

&lt;p&gt;Retry the same small write, then check the mount’s capacity again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo test&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /srv/lab/new.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /srv/lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write should print &lt;code&gt;test&lt;/code&gt;. The filesystem should show available space again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test
/dev/loop0 59M 2.0M 57M 4% /srv/lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IP, PID, and session numbers used in examples are placeholders where relevant; this walkthrough’s sample output does not include them. On a real server, confirm the mount and the file before removing anything, since deleting the wrong file can disrupt a service or remove needed data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treating every “No space left on device” error as an inode problem. Check filesystem usage first; in this case, bytes are full and inodes are fine.&lt;/li&gt;
&lt;li&gt;Assuming the root disk is full. The example concerns &lt;code&gt;/srv/lab&lt;/code&gt;, a separate volume, so inspect the filesystem that contains the affected path.&lt;/li&gt;
&lt;li&gt;Deleting a large file just because it appears in a usage report. Confirm it is a known leftover before removing it.&lt;/li&gt;
&lt;li&gt;Misreading the &lt;code&gt;lost+found&lt;/code&gt; permission message as the cause. It only reports that &lt;code&gt;du&lt;/code&gt; could not read that directory.&lt;/li&gt;
&lt;li&gt;Expecting the original large file to remain after removal. The verification checks a new write and confirms that space is available again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a clean Ubuntu box to practice on, PowerVPS is a reasonable choice: &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;https://powervps.net/?from=32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch more fixes on Server Lab: &lt;a href="https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Fix Cannot Connect to Docker Daemon on Ubuntu 24.04</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Sat, 17 Oct 2026 13:30:45 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/fix-cannot-connect-to-docker-daemon-on-ubuntu-2404-8dj</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/fix-cannot-connect-to-docker-daemon-on-ubuntu-2404-8dj</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; On a test VM with no containers you need, check whether Docker is inactive and start it with &lt;code&gt;sudo systemctl start docker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/jw95_jVQmGc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;You run a Docker command on Ubuntu 24.04 and get a connection error instead of a container list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the Docker client cannot reach the daemon through its default Unix socket. The command itself may be fine; the daemon may simply be stopped.&lt;/p&gt;

&lt;p&gt;The steps below reproduce that situation by stopping Docker, check its state, start it again, and verify that it responds. Use this procedure only on a test virtual machine with no containers you need. It uses a local Docker Engine with its default socket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Docker has a client and a daemon. The &lt;code&gt;docker&lt;/code&gt; command is the client; the daemon runs as a system service and handles requests such as listing containers. If the daemon is not running, the client cannot get a response through &lt;code&gt;/var/run/docker.sock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On this test VM, the socket and Docker services are stopped deliberately to reproduce the failure. The &lt;code&gt;sudo docker ps&lt;/code&gt; command then demonstrates what the error looks like. Checking the Docker service state helps confirm the cause before you start it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a safe test environment.&lt;/strong&gt; Do not stop Docker on a real server or a machine running containers you depend on. These commands reproduce the issue by stopping socket activation first and then stopping Docker. After that, list containers with &lt;code&gt;sudo&lt;/code&gt; to see the connection error. IP addresses, process IDs (PIDs), and session numbers in examples are placeholders where relevant.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker.socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check whether Docker is active.&lt;/strong&gt; Query the service state:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl is-active docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this reproduction, the expected sample output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   inactive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;inactive&lt;/code&gt; confirms that the Docker service is stopped. If your situation differs, use the state shown by the command to guide your diagnosis rather than assuming the daemon is down.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start the Docker service.&lt;/strong&gt; Once you have confirmed that the service is inactive, start it with system control:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting Docker should make the daemon available again through its default Unix socket.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repeat the container listing.&lt;/strong&gt; Use the same listing command to check whether the client can now reach the daemon:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to verify
&lt;/h2&gt;

&lt;p&gt;A successful response includes the container table header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header proves the daemon responded to the request. An empty list is fine: it means Docker answered, and there are no running containers to show. The important change is that the command now returns the table instead of the connection error.&lt;/p&gt;

&lt;p&gt;If you still see a connection error, check the service state again with the command above and make sure the start step completed. Keep this reproduction limited to a test VM; stopping Docker on a server can interrupt workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skipping the safety check:&lt;/strong&gt; stopping the socket and Docker services is only for a test VM with no containers you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starting the wrong thing first:&lt;/strong&gt; the reproduction stops &lt;code&gt;docker.socket&lt;/code&gt; before &lt;code&gt;docker&lt;/code&gt;. Follow that order when recreating the issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistaking an empty list for failure:&lt;/strong&gt; the table header means the daemon responded, even when no containers appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating the client as the daemon:&lt;/strong&gt; &lt;code&gt;docker ps&lt;/code&gt; requests information; the Docker service must be running to answer it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a clean Ubuntu box to practice on, PowerVPS is a reasonable choice: &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;https://powervps.net/?from=32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch more fixes on Server Lab: &lt;a href="https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Fix externally-managed-environment on Ubuntu 24.04 with venv</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:30:46 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/fix-externally-managed-environment-on-ubuntu-2404-with-venv-177k</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/fix-externally-managed-environment-on-ubuntu-2404-with-venv-177k</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Install &lt;code&gt;python3-venv&lt;/code&gt;, create a virtual environment for your app, and use that environment’s pip to install FastAPI.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/OxYTH6spsZw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;On Ubuntu 24.04, a pip install into the system Python can fail with this message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: externally-managed-environment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, the command below tries to install FastAPI into the Python environment managed by Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error can be confusing if you are used to running pip directly on other systems. It does not mean Python is broken, and it does not mean FastAPI is unavailable. Ubuntu is stopping pip from changing packages in its system-managed Python environment.&lt;/p&gt;

&lt;p&gt;The practical fix is to create a separate virtual environment for your application. Install the application dependency there, keeping it apart from packages installed and maintained by Ubuntu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Ubuntu uses apt to manage its system Python packages. That Python installation is part of the operating system, and other system tools may rely on its packages and versions. If pip changes those packages independently, it can create conflicts or leave system software in an unexpected state.&lt;/p&gt;

&lt;p&gt;The externally managed environment error is a guardrail: it prevents pip from writing into that protected system environment. For an application, use a virtual environment instead. A venv gives the app its own place for Python dependencies, separate from system packages.&lt;/p&gt;

&lt;p&gt;On a clean Ubuntu image, you may need to install the venv support package first. Without it, creating the environment can fail with an ensurepip error. Installing &lt;code&gt;python3-venv&lt;/code&gt; with apt provides the support needed for the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;First, reproduce the issue by attempting the system-level install. This command is included to show what triggers the error; it is not the command to use for installing the app dependency.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu may respond with &lt;code&gt;error: externally-managed-environment&lt;/code&gt;. That is the expected protection for the managed Python environment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the venv support package and create a virtual environment for the app. The command uses apt for Ubuntu’s system package and then creates the environment at &lt;code&gt;~/app/.venv&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv ~/app/.venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the app directory does not exist, venv creation may report an error. In this example, &lt;code&gt;~/app/.venv&lt;/code&gt; is the target path; keep the same path in the install and verification steps so they use the environment you created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install FastAPI using the pip executable inside the virtual environment.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ~/app/.venv/bin/pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the environment’s pip directly. Activation is optional: a full executable path makes clear which pip is being run and avoids relying on shell activation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to verify
&lt;/h2&gt;

&lt;p&gt;Ask the virtual environment’s pip to show FastAPI’s package information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/app/.venv/bin/pip show fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output includes the package name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: fastapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That name confirms FastAPI is installed in the environment queried by this command. The important detail is the executable path: &lt;code&gt;~/app/.venv/bin/pip&lt;/code&gt; points to the app’s virtual environment, rather than Ubuntu’s system pip. If you use this approach for another application dependency, use the same environment’s pip path for both installation and inspection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Installing into system Python again:&lt;/strong&gt; Running &lt;code&gt;python3 -m pip install fastapi&lt;/code&gt; repeats the operation that Ubuntu blocks. Use the venv’s pip path for the application package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping &lt;code&gt;python3-venv&lt;/code&gt;:&lt;/strong&gt; On a clean image, venv support may be missing, and environment creation can stop with an ensurepip error. Install the Ubuntu package before creating the venv.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using a different pip by accident:&lt;/strong&gt; A bare &lt;code&gt;pip&lt;/code&gt; command may refer to a different Python environment. The full path in these steps makes the target explicit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking the wrong environment:&lt;/strong&gt; Run &lt;code&gt;pip show&lt;/code&gt; through the same &lt;code&gt;~/app/.venv/bin/pip&lt;/code&gt; path used for installation. Otherwise, the result may describe a different Python environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming activation is required:&lt;/strong&gt; You can use the virtual environment’s executable paths directly, so these steps do not depend on activation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating sample details as literal server values:&lt;/strong&gt; Any IP addresses, process IDs (PIDs), or session numbers in examples are placeholders where relevant; they are not values to copy as part of this Python fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a clean Ubuntu box to practice on, PowerVPS is a reasonable choice: &lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;https://powervps.net/?from=32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch more fixes on Server Lab: &lt;a href="https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCov7rfWAnS4c2wF5E9V1Yag&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Automate Server Provisioning with Bash Scripts</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:00:23 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/how-to-automate-server-provisioning-with-bash-scripts-e4l</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/how-to-automate-server-provisioning-with-bash-scripts-e4l</guid>
      <description>&lt;p&gt;A new VM came up with the wrong firewall defaults, and a “quick” manual fix turned into 40 minutes of checking package state and user permissions. The next time, I wrote down the steps as a script—and made it safe to run again. In this guide, we’ll build a small, repeatable Bash bootstrap for Debian and Ubuntu servers, then cover how to run and verify it without locking yourself out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what the script should own
&lt;/h2&gt;

&lt;p&gt;A provisioning script is most useful when it handles a short list of predictable tasks: install baseline packages, create an administrative user, install that user’s public SSH key, and set basic firewall rules.&lt;/p&gt;

&lt;p&gt;Keep application deployment, secrets, and provider-specific networking out of a first-pass script. Those concerns vary by project and environment. This example also &lt;strong&gt;does not&lt;/strong&gt; change SSH daemon settings or disable password authentication; make those changes only after confirming key-based access works.&lt;/p&gt;

&lt;p&gt;The script below expects a fresh Debian or Ubuntu server with &lt;code&gt;systemd&lt;/code&gt; and &lt;code&gt;apt&lt;/code&gt;. Run it from the provider’s console or over an existing SSH session as root. Don’t test a new access setup by closing your only working session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the provisioning script
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;provision.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail

log&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'[%s] %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-Is&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

fail&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'ERROR: %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$EUID&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Run this script as root."&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Set ADMIN_USER."&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PUBLIC_KEY_FILE&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Set PUBLIC_KEY_FILE."&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^[a-z_][a-z0-9_-]&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$]&lt;/span&gt;?&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Invalid ADMIN_USER."&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUBLIC_KEY_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Cannot read PUBLIC_KEY_FILE."&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# shellcheck disable=SC1091&lt;/span&gt;
  &lt;span class="nb"&gt;source&lt;/span&gt; /etc/os-release
&lt;span class="k"&gt;else
  &lt;/span&gt;fail &lt;span class="s2"&gt;"Cannot identify operating system."&lt;/span&gt;
&lt;span class="k"&gt;fi

case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;ubuntu|debian&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; fail &lt;span class="s2"&gt;"This script supports Debian and Ubuntu only."&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

log &lt;span class="s2"&gt;"Updating package metadata and installing baseline tools."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
apt-get update
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="se"&gt;\&lt;/span&gt;
  fail2ban &lt;span class="se"&gt;\&lt;/span&gt;
  git &lt;span class="se"&gt;\&lt;/span&gt;
  ufw

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;useradd &lt;span class="nt"&gt;--create-home&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt; /bin/bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

if &lt;/span&gt;getent group &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;elif &lt;/span&gt;getent group wheel &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; wheel &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;fail &lt;span class="s2"&gt;"Could not find a sudo or wheel group."&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;home_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;getent passwd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="nt"&gt;-f6&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; fail &lt;span class="s2"&gt;"Could not find the user's home directory."&lt;/span&gt;

&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 700 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home_dir&lt;/span&gt;&lt;span class="s2"&gt;/.ssh"&lt;/span&gt;
&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 600 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUBLIC_KEY_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$home_dir&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/authorized_keys"&lt;/span&gt;

systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban

log &lt;span class="s2"&gt;"Configuring UFW. Allowing SSH before enabling the firewall."&lt;/span&gt;
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nb"&gt;enable

&lt;/span&gt;log &lt;span class="s2"&gt;"Provisioning complete for &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;750 provision.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script uses &lt;code&gt;set -Eeuo pipefail&lt;/code&gt; so command failures and unset variables don’t pass silently. It also checks its operating system and inputs before making changes. The package install and user creation can be repeated; UFW rules may report that they already exist, but the final policy remains the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it with a public key
&lt;/h2&gt;

&lt;p&gt;On the server, place the administrator’s &lt;strong&gt;public&lt;/strong&gt; key in a readable file, such as &lt;code&gt;/root/admin.pub&lt;/code&gt;. Do not copy a private key to the server. Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo env&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;PUBLIC_KEY_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root/admin.pub &lt;span class="se"&gt;\&lt;/span&gt;
  ./provision.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re already root, you can omit &lt;code&gt;sudo env&lt;/code&gt; and set the variables directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy &lt;span class="nv"&gt;PUBLIC_KEY_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root/admin.pub ./provision.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the user exists, the services are active, and UFW allows SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id &lt;/span&gt;deploy
systemctl is-active fail2ban
ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a &lt;strong&gt;second terminal&lt;/strong&gt; and test the new account before ending your original session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519 deploy@your-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the key path and server name with your own values. The account will need a public key whose matching private key is available on your client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be careful with firewall and SSH changes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ufw allow OpenSSH&lt;/code&gt; uses the firewall application profile named &lt;code&gt;OpenSSH&lt;/code&gt;. Check that the profile exists with &lt;code&gt;ufw app list&lt;/code&gt;; if your SSH daemon listens on a nonstandard port, allow that port &lt;strong&gt;before&lt;/strong&gt; enabling UFW. Cloud firewalls and network ACLs are separate from UFW, so check both layers if SSH is unreachable.&lt;/p&gt;

&lt;p&gt;A practical tip from experience: don’t combine “create a new admin user,” “change the SSH port,” and “disable password login” into one untested run. That makes it harder to identify the cause of a lockout. First confirm key-based login in a second session; then change SSH settings in a separate, reversible step.&lt;/p&gt;

&lt;p&gt;Another useful habit is to keep the script in version control and test it on a throwaway VM before using it on a long-lived server. Shell scripts are executable infrastructure: a typo in a firewall rule can have a real outage attached to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it repeatable, not magical
&lt;/h2&gt;

&lt;p&gt;Bash is a good fit for a small bootstrap, but it isn’t a full configuration-management system. This example doesn’t manage drift—for example, it won’t remove packages you no longer want or detect every manual change. Keep the script focused, review changes before re-running it on production, and log each run somewhere appropriate for your environment.&lt;/p&gt;

&lt;p&gt;For more complex setups, consider adding a dry-run mode, writing a run log to a protected file, and testing with ShellCheck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shellcheck provision.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need frequent changes across many servers, tools such as Ansible can provide clearer state management. A Bash script can still handle the initial bootstrap that installs the prerequisites.&lt;/p&gt;

&lt;p&gt;For compute, I’ve used/tested &lt;strong&gt;&lt;a href="https://powervps.net/?from=32" rel="noopener noreferrer"&gt;PowerVPS&lt;/a&gt;&lt;/strong&gt; for general-purpose VPS workloads and &lt;strong&gt;&lt;a href="https://cloud.vast.ai/?ref_id=588745" rel="noopener noreferrer"&gt;Vast.ai&lt;/a&gt;&lt;/strong&gt; when I need to rent GPU capacity. Availability, pricing, and setup options depend on the workload and region, so compare them against your requirements. The &lt;strong&gt;&lt;a href="https://serverrental.store" rel="noopener noreferrer"&gt;Server Rental Guide&lt;/a&gt;&lt;/strong&gt; is also a useful starting point when comparing rental options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A useful provisioning script is small, explicit, and safe to repeat. This one installs a few baseline packages, creates a sudo-capable user with a public SSH key, enables Fail2ban, and configures UFW without rewriting SSH settings.&lt;/p&gt;

&lt;p&gt;Test it on a disposable VM, verify access in a second session, and keep firewall and SSH changes deliberate. That modest discipline turns a pile of one-off commands into a dependable starting point for the next server.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What AI Video Actually Cost Us on Rented GPUs</title>
      <dc:creator>Big Mazzy</dc:creator>
      <pubDate>Fri, 16 Oct 2026 08:00:02 +0000</pubDate>
      <link>https://dev.to/big_mazzy_06d057cc24398c5/what-ai-video-actually-cost-us-on-rented-gpus-ac7</link>
      <guid>https://dev.to/big_mazzy_06d057cc24398c5/what-ai-video-actually-cost-us-on-rented-gpus-ac7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; We rent GPUs on Vast.ai for AI video work. In one MiniMax H3 image-to-video run, generation cost about $0.03 per five-second clip; the full session cost $3.59 for 82 clips. The H3 API price in our notes was $0.08 per video second, or $0.40 for a five-second clip. Rental won clearly for that batch, but the $0.03 figure excludes setup and idle time. We use the full-session number for budgeting, count failed generations, and choose APIs when avoiding infrastructure work matters more than the possible savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the unit you need
&lt;/h2&gt;

&lt;p&gt;“Cost per clip” can mean at least two things: the cost while a model is generating, or the total bill divided by usable clips. Both are useful, but they answer different questions.&lt;/p&gt;

&lt;p&gt;For a rented GPU, our basic calculation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generation cost per clip = clip runtime in seconds × hourly instance rate / 3,600

session cost per usable clip = total session bill / usable clips

session cost per usable video minute =
    total session bill / (usable video seconds / 60)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rate should include disk when disk is billed separately. The session bill also captures time spent starting the instance, downloading weights, fixing problems, and waiting between jobs. If a generation fails or produces a clip we cannot use, it still consumes GPU time and belongs in the denominator calculation.&lt;/p&gt;

&lt;p&gt;For an API billed by output duration, the simple calculation is &lt;code&gt;price per video second × clip duration&lt;/code&gt;. Check the provider’s current pricing terms before applying it: model, resolution, audio, and billing rules can affect the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our measured rental costs
&lt;/h2&gt;

&lt;p&gt;Here are the runs behind our estimates. The per-clip numbers describe different accounting boundaries, so the session column matters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Rented GPU&lt;/th&gt;
&lt;th&gt;Clip and runtime&lt;/th&gt;
&lt;th&gt;Recorded cost&lt;/th&gt;
&lt;th&gt;Session context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax H3 image-to-video&lt;/td&gt;
&lt;td&gt;A100 SXM4 80GB in Czechia, $1.19/h GPU; about $1.25/h with disk&lt;/td&gt;
&lt;td&gt;Five-second clip, about 86 seconds to generate&lt;/td&gt;
&lt;td&gt;About $0.03 in generation time&lt;/td&gt;
&lt;td&gt;82 hero clips; $3.59 total, about three hours including setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax H3 text-to-video with audio, earlier test&lt;/td&gt;
&lt;td&gt;RTX PRO 6000 Blackwell 96GB, $1.625/h&lt;/td&gt;
&lt;td&gt;Five-second clip, about 51 seconds to generate&lt;/td&gt;
&lt;td&gt;About $0.02 in GPU time&lt;/td&gt;
&lt;td&gt;56-minute session; $2.18 total, including $0.64 inbound traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LatentSync 1.6 lip-sync&lt;/td&gt;
&lt;td&gt;RTX 3090 in UAE, $0.27/h, free inbound&lt;/td&gt;
&lt;td&gt;Eight-second talk clip, 238 seconds to process&lt;/td&gt;
&lt;td&gt;About $0.09 recorded for the clip&lt;/td&gt;
&lt;td&gt;A separate queued batch self-test took 15.9 minutes and cost $0.07; setup took 11 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The H3 A100 arithmetic is straightforward: &lt;code&gt;86 × $1.25 / 3,600&lt;/code&gt; is about $0.03. At that rate, twelve five-second clips make one minute of output, so generation alone is about $0.36 per output minute. Our actual 82-clip session delivered 410 seconds of video. Dividing $3.59 by those 410 seconds gives about &lt;strong&gt;$0.53 per output minute&lt;/strong&gt;, or about &lt;strong&gt;$0.044 per clip&lt;/strong&gt;. That session figure is the more useful one for a budget.&lt;/p&gt;

&lt;p&gt;The lip-sync row deserves care. At $0.27/h, 238 seconds of GPU time calculates to about $0.02, while our recorded clip cost was about $0.09. Those figures do not describe the same accounting boundary; the available notes do not fully reconcile them. We would use the recorded cost for a rough budget and measure a new batch before treating it as a repeatable unit price. At $0.09 per eight-second clip, the rough output-minute figure is $0.675.&lt;/p&gt;

&lt;p&gt;This small Python calculation shows the boundaries explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;h3_rate_with_disk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;
&lt;span class="n"&gt;h3_runtime_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;
&lt;span class="n"&gt;h3_clip_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;h3_session_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.59&lt;/span&gt;
&lt;span class="n"&gt;h3_session_clips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;

&lt;span class="n"&gt;api_rate_per_video_second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;

&lt;span class="n"&gt;generation_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h3_runtime_seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h3_rate_with_disk&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="n"&gt;session_cost_per_clip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h3_session_cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;h3_session_clips&lt;/span&gt;
&lt;span class="n"&gt;session_output_minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h3_session_clips&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h3_clip_seconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="n"&gt;api_cost_per_clip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_rate_per_video_second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h3_clip_seconds&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H3 rental, generation only: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;generation_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/clip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H3 rental, full session: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_cost_per_clip&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/clip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H3 rental, full session: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h3_session_cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;session_output_minutes&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/output minute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H3 API at stated rate: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_cost_per_clip&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/clip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compare APIs fairly
&lt;/h2&gt;

&lt;p&gt;Our H3 API reference price is $0.08 per second of video. That makes a five-second clip $0.40 and a minute of output $4.80 at that rate. For 82 five-second clips, the duration-based calculation is $32.80. Against our $3.59 rental session, that is roughly nine times the cost. Comparing only the $0.03 generation figure makes rental look about thirteen times cheaper, but hides the rest of the session.&lt;/p&gt;

&lt;p&gt;We do not have measured, directly comparable API prices for every model people might consider:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API option&lt;/th&gt;
&lt;th&gt;Price to use in a current comparison&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax H3&lt;/td&gt;
&lt;td&gt;$0.08 per video second in our reference&lt;/td&gt;
&lt;td&gt;Confirm current rate and settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Veo&lt;/td&gt;
&lt;td&gt;Check current pricing&lt;/td&gt;
&lt;td&gt;Model, output duration, resolution, and billing terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kling&lt;/td&gt;
&lt;td&gt;Check current pricing&lt;/td&gt;
&lt;td&gt;Model, output duration, resolution, and billing terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance&lt;/td&gt;
&lt;td&gt;Check current pricing&lt;/td&gt;
&lt;td&gt;Model, output duration, resolution, and billing terms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is a budgeting prompt, not a claim that the outputs are interchangeable. Compare results that meet the same creative need. A cheaper clip that must be regenerated several times may cost more per accepted clip; an expensive clip that works immediately may save time. We would also compare the actual workflow offered by each API, rather than assume every model supports the same inputs and controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the break-even point moves
&lt;/h2&gt;

&lt;p&gt;A useful planning formula is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rental total = hourly rate × (setup + generation + idle time) / 60
             + transfer charges + ongoing storage charges

API total = accepted and rejected billable output × API rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that rental formula, time is in minutes. For a quick estimate, divide expected rental total by the number of &lt;strong&gt;usable&lt;/strong&gt; clips, then compare it with the API cost of obtaining those usable clips. The difficult inputs are usually how many attempts you will reject and how long the rented instance will sit idle.&lt;/p&gt;

&lt;p&gt;Setup makes batch size important. Our H3 run downloaded 72 GB of weights in about eight minutes, and the full 82-clip session took about three hours including setup. For LatentSync, setup took roughly 9–11 minutes; in the queued self-test, that was most of a 15.9-minute run. When we queue work before renting, more output shares that startup time.&lt;/p&gt;

&lt;p&gt;Idle time moves the answer the other way. A GPU meter can run while we inspect clips, revise prompts, or wait for the next input. Failed generations also increase cost per accepted clip. The H3 session produced 82 hero clips, but that count should not be treated as a guarantee that another batch will have the same acceptance rate. Our own time belongs in the decision too: preparing images, handling weights and drivers, watching jobs, copying results, and cleaning up are work even when the provider does not put them on the invoice.&lt;/p&gt;

&lt;p&gt;APIs are often the better choice for a few clips, an uncertain experiment, or work that must start without environment setup. They also remove much of the operational burden when the team cannot queue a batch or does not want to manage an instance. Rental becomes more attractive when we have many ready inputs, a known workflow, and someone available to check the run and collect its outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid charges that spoil the calculation
&lt;/h2&gt;

&lt;p&gt;The earlier Blackwell test taught us to inspect inbound traffic pricing. Its $2.18 total included $0.64 for 245 GB of model weights; GPU time accounted for $1.47. We now check &lt;code&gt;inet_down_cost&lt;/code&gt; when looking at offers and prefer zero inbound cost for weight-heavy work. We also filter for reliability of at least 0.97, check that the driver and CUDA version fit the image, and provision enough disk. For large weights, our working threshold is at least 150 GB; 40 GB is suitable only for smaller downloads.&lt;/p&gt;

&lt;p&gt;The CLI can help inspect offers and verify what is still running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vastai search offers &lt;span class="s1"&gt;'reliability &amp;gt;= 0.97 inet_down_cost = 0 disk_space &amp;gt;= 150'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'dph+'&lt;/span&gt; &lt;span class="nt"&gt;--raw&lt;/span&gt;
vastai show instances &lt;span class="nt"&gt;--raw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We made a more painful mistake with a stopped instance. Its 90 GB disk continued billing at about $0.0167/h, roughly $0.40 a day. We left it stopped for days, our balance went negative, and we could not start it to retrieve a trained result that we had never downloaded. “Stopped” was not the end of the bill.&lt;/p&gt;

&lt;p&gt;Our rule now is to copy results back, back them up, and verify the backup before destroying the instance. In one verification, local and remote counts and sizes matched: 5,342 files and 1.379 GiB. For long runs, we use a cost cap and a detached deadline watchdog that retries destruction if the controller crashes. We then verify that the instance list is empty. The CLI destruction command prompts for confirmation; this is the form we use in automation after results are safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;y | vastai destroy instance &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTANCE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
vastai show instances &lt;span class="nt"&gt;--raw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What we would budget
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What it cost us&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;H3 image-to-video on an A100: about $0.03 of generation time per five-second clip; $3.59 for 82 clips over about three hours, or about $0.044 per clip across the session.&lt;/li&gt;
&lt;li&gt;Earlier H3 text-to-video test on Blackwell: $2.18 for 56 minutes, including $1.47 of GPU time and $0.64 of inbound traffic.&lt;/li&gt;
&lt;li&gt;LatentSync 1.6 on an RTX 3090: about $0.09 recorded for an eight-second talk clip. A separate 15.9-minute queued self-test cost $0.07.&lt;/li&gt;
&lt;li&gt;An overlooked stopped 90 GB disk: about $0.0167/h, or about $0.40/day.&lt;/li&gt;
&lt;li&gt;H3 API reference: $0.08 per video second, equivalent to $0.40 for a five-second clip at that rate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;For our 82-clip H3 batch, rental saved enough to justify managing the instance. We would still price a small or unfamiliar job both ways, using current API pages and an estimate for setup, rejected output, idle time, and our own work. If you want to run that comparison on Vast.ai, &lt;a href="https://cloud.vast.ai/?ref_id=588745" rel="noopener noreferrer"&gt;this is our referral link&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gpu</category>
      <category>ai</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
