Hello; I've updated the documentation to clarify this, but it's available starting in ProGet 5.2.9. So, you'll need to upgrade to enable it :)
atripp
@atripp
C# developer by trade, but writing less and less code and more and more specs.
Best posts made by atripp
-
RE: Service Health API call returning 404posted in Support
-
RE: NPM Connector returns plus "+" in versionsposted in Support
Thanks for the update! I've noted this in the docs, and linked to this discussion :)
https://github.com/Inedo/inedo-docs/commit/d24087911584bbda833314084a58c2ae1ff41c39
-
RE: [ProGet] [NativeApi] NpmPackages_DeletePackage not working.posted in Support
Hello,
That API will only delete package metadata from the database, not from disk. It's mostly intended for internal use only, and probably shouldn't be exposed to the API. In any case, we don't store the
@with internally, so if you change@myscopetomyscopeit should work.Note that the NPM doesn't provide a way to delete packages, and we never implemented it. There hasn't been any demand for it to date, as people don't really delete packages programmatically - but you're definitely welcome to submit a feature request and help us understand why it'd be a value (like, the workflow you use that requires deleting packages, etc).
Alana
-
RE: Creating PowerShell repository, protecting pull/download by API keyposted in Support
Hello, for sure!
It's pretty easy; just don't give the
Anonymoususer any access to your feeds, and then authentication will always be required, either when browsing the ProGet application or using the API (such asInstall-Module).When you use the Register-PSRepository command, you can the
Credentialoption to specify a credential.This credential can be the name/password of a user inside of ProGet (let's say,
Admin:Admin), or it can be username ofapiwith a password of an api key you've configured (so,api:my-secret-key). -
RE: Restricting API access to View/Downloadposted in Support
Hello;
The Native API is for low, system-level functions, and it's "all or nothing". If you give someone access to Native API, you are effectively making them an administrator, as they can also change permissions and grant admin privileges. So, I don't think you want this. Instead, you'll want to use the Debian API endpoint that we implement.
It's a third-party API format
In order to support third-party package formats types like NuGet, npm, etc., ProGet implements a variety of third-party APIs. We only provide minimal documentation for these APIs, as they are generally either already documented elsewhere. However, you can generally find the basics by searching for specific things you'd like to do with the API, such as "how to search for packages using the NuGet API" or "how to publish an npm package using the API".
So in this case, I recommend to search "how to view and download apt packages".
-
RE: PyPI package not shown in search results accessible via urlposted in Support
I'm not very familiar with PyPi packages, but I know there are some oddities with
-and_, and that they are sometimes supposed to be treated the same, and sometimes not. We don't totally understand all the rules, to be honest (even after reading PEP503 specifications).In this case, the package is actually
websocket_client, notwebsocket-client.See: https://pypi.org/project/websocket_client/
When you search for
websocket_clientin ProGet, it shows up, as expected. -
RE: How to find out package disk space?posted in Support
In ProGet 5.3, we plan to have a couple tabs on each
Tag(i.e. container image) that would provide this info: Metadata (will be a key/value pair of a bunch of stuff), andLayerswill show details about each of these layers.That might help, but otherwise, we have retention policies which are designed to clean up old and unused images.We'll also have a way to detect which images are actually being used :)
-
RE: [BUG - ProGet] Not able to remove container descriptionposted in Support
As @apxltd mentioned, we've got a whole bunch planned for ProGet 5.3.
I've logged this to our internal project document, and if it's easy to implement in ProGet 5.2 (I can't imagine it wouldn't be), we'll log it as a bug and ship in a maintence release.
Do note, this is not an IMAGE description, it's a REPOSITORY (i.e. a collection of images with the same name, like
MyCoolContainerApp) description; so this means the description will be there on all images/tags in the repository. -
RE: [Question - ProGet] Are versions amount wrong ?posted in Support
You're right, I guess that's showing the "layers" instead of the "tags"; I think it should be showing container registries separately (they're not really feeds), but that's how it's represented behind the scenes now.
Anyways we are working on ProGet 5.3 now; there's a whole bunch of container improvements coming, so I've noted this on our internal project document, to make sure we get a better display for container registries.
-
RE: Anonymous user can see list of packages and containersposted in Support
@Stephen-Schaff thanks for the bug report, I verified that this may happen depending on permission of user, and which feeds they can/can't use --- but it seems an easy enough fix that we can do via PG-1894 (targeted to next release) - the packages can't be viewed upon clicking, but it's a sub-optimal experience for showing packages they can't see
Latest posts made by atripp
-
RE: Support for Air-gapped environmentsposted in Support
Hi @steviecoaster ,
Offline / air-gapped installation is common and a documented use-case:
https://docs.inedo.com/docs/installation/windows/inedo-hub/offlineAs the article mentions, you can download the "offline installer", which is essentially a
self-extracting zip file that runs a Custom Installer created using the Inedo Hub.That
.exefile is not suitable for automation, so if that's a requirement then you'll need to use an alternative if you wanted to automate upgrade/installation. That article outlines a few concepts, but ultimately it really depends how "air-gapped" we're talking here.If we're talking a SCIF with "security-guard inspected installation media", then I don't think automation is really going to really get you much ;)
Thanks,
Alana -
RE: Allow networkservice to use the DB in Progetposted in Support
Hi @reseau_6272 ,
Just to confirm, you've switched the ProGet service from a domain account to use Network Service, and when starting the service you're getting some kind of permission error from SQL Server?
The easiest solution is to simply switch to using a username/password instead of Windows Integrated Authentication and edit the connection string appropriately. Keeping in mind that, eventually, you will need to move away from SQL Server and migrate to PostgreSQL, which will not have these issues.
Otherwise, you will explicitly need to grant a login to the machine account. Network Service is represented in SQL Server as the machine account (e.g., DOMAIN\MACHINENAME$), and the identity needs to be explicitly created
CREATE LOGIN [MYDOMAIN\WEB01$] FROM WINDOWS;before you can assign permissions.Thanks,
Alana -
RE: [ProGet] Unexpected redirect when accessing Maven package with non-standard version starting with a characterposted in Support
Good news, it's available now for testing! We're considering merging to ProGet 2025, or maybe keeping for ProGet 2026?
Anyway, I posted a lot more detail now:
https://forums.inedo.com/topic/5696/proget-is-unable-to-download-maven-packages-that-use-a-nonstandard-versioning-scheme/2Thanks,
AlanaFYI -- locked this, in case anyone has comments/questions on that change, I guess that post will be the "official" thread at this point :)
-
RE: Symbol Server id issueposted in Support
Hi @it_9582 ,
Thanks for checking that! Well, I'm not sure then :)
From here, how about sending us the package? Then I can upload it and see about debugging in ProGet to find out where it's coming from.
If you can open a ticket and reference QA-3010 somewhere it'll link the issues right-up on our dashboard. Then you can attach the file to that ticket.
We'll respond on there, and eventually update this thread once we figure out the issue.
Thanks,
Alana -
RE: Proget is unable to download Maven packages that use a nonstandard versioning schemeposted in Support
Thanks for the inquiry! The changes are available in the
inedo/proget:25.0.24-ci.4container image, and we'd love to get a second set of eyes. Are you using Docker?They're relatively simple, but we just avoid changing stuff like this in maintenance releases... so it's currently slated for ProGet 2026.
But it should be okay for a maintenance release. Please let us know, we'll decide to release based on your or other user feedback.
Here's what we changed.
First, we added a "sixth" component called
IncrementalVersion2that will support versions like1.2.3.4-mybuild-678(where4is the second incrementing version), so that vulnerability identification can work better. Our implementation is based on the the Maven version specs, which in retrospect, seems to be followed only by ProGet. Pretty low risk here.Second, we changed our "path parsing" logic, which identifies the groupId, artifactId, version, artifactType from a string like
/junit/junit/4.8.2/junit-4.8.2.jarinto/mygroup/more-group/group-42/my-artifact/1.0-SNAPSHOT/maven-metadata.xml.It's a little hard to explain, so I'll just share the new and old logic:
//OLD: if (urlPartsQ.TryPeek(out string? maybeVersion) && char.IsNumber(maybeVersion, 0)) if (urlPartsQ.TryPeek(out string? maybeVersion) && ( char.IsNumber(maybeVersion, 0) || maybeVersion.EndsWith("-SNAPSHOT", StringComparison.OrdinalIgnoreCase) || (this.FileName is not null && !this.FileName.Equals("maven-metadata.xml", StringComparison.OrdinalIgnoreCase)) )) { this.Version = maybeVersion; urlPartsQ.Pop(); }Long story short, this seems to work fine for
v8.5.0and shouldn't break unless someone is uploading improperly named artifact files (e.g.my-group/my-artifact/version-1000/maven-metadata.xmlor e.g.my-photo/cool-snapshot/hello-kitty.jpg).Thanks,
Alana -
RE: [ProGet] Unexpected redirect when accessing Maven package with non-standard version starting with a characterposted in Support
Hi @koksime-yap_5909 ,
Just as a quick update! Given that this is a more wide-spread problem, we've fixed the code and plan to release in ProGet 2026 (or possibly sooner, if we can make it low-risk enough in a mainteannce release).
Thanks,
Alana -
RE: Symbol Server id issueposted in Support
Hi @it_9582 ,
Sorry it looks like we're dealing with a lot more code than I expected we would. I really don't know what to look at, and neither your code or our code makes sense (it's been many many years since anyone edited it.
I'm not sure if it's helpful, but I'll share the code of our class. If you spot anything simple to change, we can explore it. Otherwise, I think the only way to move forward would be for you to share us some example nuget packages that we can set a debugger to.
Here's the
MicrosoftPdbFileclass, which i combined into one giant string here:using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; namespace Inedo.ProGet.Symbols; /// <summary> /// Provides access to the data contained in a Microsoft PDB file. /// </summary> public sealed class MicrosoftPdbFile : IDisposable, IPdbFile { private RootIndex root; private Dictionary<string, int> nameIndex; private bool leaveStreamOpen; private bool disposed; /// <summary> /// Initializes a new instance of the <see cref="MicrosoftPdbFile"/> class. /// </summary> /// <param name="stream">Stream which is backed by a PDB file.</param> /// <param name="leaveStreamOpen">Value indicating whether to leave the stream open after this instance is disposed.</param> public MicrosoftPdbFile(Stream stream, bool leaveStreamOpen) { if (stream == null) throw new ArgumentNullException(nameof(stream)); this.leaveStreamOpen = leaveStreamOpen; this.Initialize(stream); } /// <summary> /// Gets the PDB signature. /// </summary> public uint Signature { get; private set; } /// <summary> /// Gets the PDB age. /// </summary> public uint Age { get; private set; } /// <summary> /// Gets the PDB guid. /// </summary> public Guid Guid { get; private set; } ImmutableArray<byte> IPdbFile.Id => this.Guid.ToByteArray().ToImmutableArray(); bool IPdbFile.IsPortable => false; /// <summary> /// Returns a stream backed by the data in a named PDB stream. /// </summary> /// <param name="streamName">Name of the PDB stream to open.</param> /// <returns>Stream backed by the specified named stream.</returns> public Stream OpenStream(string streamName) { if (streamName == null) throw new ArgumentNullException(nameof(streamName)); int? streamIndex = this.TryGetStream(streamName); if (streamIndex == null) throw new InvalidOperationException($"Stream {streamName} was not found."); return this.root.OpenRead((int)streamIndex); } /// <summary> /// Returns an enumeration of all of the stream names in the PDB file. /// </summary> /// <returns>Enumeration of all stream names.</returns> public IEnumerable<string> EnumerateStreams() => this.nameIndex.Keys; /// <summary> /// Returns an enumeration of all of the source file names in the PDB file. /// </summary> /// <returns>Enumeration of all of the source file names.</returns> public IEnumerable<string> GetSourceFileNames() { var srcFileNames = this.EnumerateStreams() .Where(s => s.StartsWith("/src/files/", StringComparison.OrdinalIgnoreCase)) .Select(s => s.Substring("/src/files/".Length)) .ToHashSet(StringComparer.OrdinalIgnoreCase); try { using (var namesStream = this.OpenStream("/names")) using (var namesReader = new BinaryReader(namesStream)) { namesStream.Position = 8; int length = namesReader.ReadInt32(); long endPos = length + 12; while (namesStream.Position < endPos && namesStream.Position < namesStream.Length) { try { var name = ReadNullTerminatedString(namesReader); if (name.Length > 0 && Path.IsPathRooted(name)) srcFileNames.Add(name); } catch { // Can't read name } } } } catch { // Can't enumerate names stream } return srcFileNames; } /// <summary> /// Closes the PDB file. /// </summary> public void Close() { if (!this.disposed) { this.root.Close(this.leaveStreamOpen); this.disposed = true; } } void IDisposable.Dispose() => this.Close(); private void Initialize(Stream stream) { var fileSignature = new byte[0x20]; stream.Read(fileSignature, 0, fileSignature.Length); this.root = new RootIndex(stream); using (var sigStream = this.root.OpenRead(1)) using (var reader = new BinaryReader(sigStream)) { uint version = reader.ReadUInt32(); this.Signature = reader.ReadUInt32(); this.Age = reader.ReadUInt32(); this.Guid = new Guid(reader.ReadBytes(16)); this.nameIndex = ReadNameIndex(reader); } } private int? TryGetStream(string name) => this.nameIndex.TryGetValue(name, out int index) ? (int?)index : null; private static Dictionary<string, int> ReadNameIndex(BinaryReader reader) { int stringOffset = reader.ReadInt32(); var startOffset = reader.BaseStream.Position; reader.BaseStream.Seek(stringOffset, SeekOrigin.Current); int count = reader.ReadInt32(); int hashTableSize = reader.ReadInt32(); var present = new BitArray(reader.ReadBytes(reader.ReadInt32() * 4)); var deleted = new BitArray(reader.ReadBytes(reader.ReadInt32() * 4)); if (deleted.Cast<bool>().Any(b => b)) throw new InvalidDataException("PDB format not supported: deleted bits are not 0."); var nameIndex = new Dictionary<string, int>(hashTableSize + 100, StringComparer.OrdinalIgnoreCase); for (int i = 0; i < hashTableSize; i++) { if (i < present.Length && present[i]) { int ns = reader.ReadInt32(); int ni = reader.ReadInt32(); var pos = reader.BaseStream.Position; reader.BaseStream.Position = startOffset + ns; var name = ReadNullTerminatedString(reader); reader.BaseStream.Position = pos; nameIndex.Add(name, ni); } } return nameIndex; } private static string ReadNullTerminatedString(BinaryReader reader) { var data = new List<byte>(); var b = reader.ReadByte(); while (b != 0) { data.Add(b); b = reader.ReadByte(); } return Encoding.UTF8.GetString(data.ToArray()); } private sealed class PagedFile : IDisposable { private LinkedList<CachedPage> pages = new LinkedList<CachedPage>(); private Stream baseStream; private readonly object lockObject = new object(); private BitArray freePages; private uint pageSize; private uint pageCount; private bool disposed; public PagedFile(Stream baseStream, uint pageSize, uint pageCount) { this.baseStream = baseStream; this.pageSize = pageSize; this.pageCount = pageCount; this.CacheSize = 1000; } public int CacheSize { get; } public uint PageSize => this.pageSize; public uint PageCount => this.pageCount; public void InitializeFreePageList(byte[] data) { this.freePages = new BitArray(data); } public byte[] GetFreePageList() { var data = new byte[this.freePages.Count / 8]; for (int i = 0; i < data.Length; i++) { for (int j = 0; j < 8; j++) { if (this.freePages[(i * 8) + j]) data[i] |= (byte)(1 << j); } } return data; } public byte[] GetPage(uint pageIndex) { if (this.disposed) throw new ObjectDisposedException(nameof(PagedFile)); if (pageIndex >= this.pageCount) throw new ArgumentOutOfRangeException(); lock (this.lockObject) { var page = this.pages.FirstOrDefault(p => p.PageIndex == pageIndex); if (page != null) { this.pages.Remove(page); } else { var buffer = new byte[this.pageSize]; this.baseStream.Position = this.pageSize * pageIndex; this.baseStream.Read(buffer, 0, buffer.Length); page = new CachedPage { PageIndex = pageIndex, PageData = buffer }; } while (this.pages.Count >= this.CacheSize) { this.pages.RemoveLast(); } this.pages.AddFirst(page); return page.PageData; } } public void Dispose() { this.baseStream.Dispose(); this.pages = null; this.disposed = true; } private sealed class CachedPage : IEquatable<CachedPage> { public uint PageIndex; public byte[] PageData; public bool Equals(CachedPage other) => this.PageIndex == other.PageIndex && this.PageData == other.PageData; public override bool Equals(object obj) => obj is CachedPage p ? this.Equals(p) : false; public override int GetHashCode() => this.PageIndex.GetHashCode(); } } private sealed class PdbStream : Stream { private RootIndex root; private StreamInfo streamInfo; private uint position; public PdbStream(RootIndex root, StreamInfo streamInfo) { this.root = root; this.streamInfo = streamInfo; } public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => false; public override long Length => this.streamInfo.Length; public override long Position { get => this.position; set => this.position = (uint)value; } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); int bytesRemaining = Math.Min(count, (int)(this.Length - this.position)); int bytesRead = 0; while (bytesRemaining > 0) { uint currentPage = this.position / this.root.Pages.PageSize; uint currentPageOffset = this.position % this.root.Pages.PageSize; var page = this.root.Pages.GetPage(this.streamInfo.Pages[currentPage]); int bytesToCopy = Math.Min(bytesRemaining, (int)(this.root.Pages.PageSize - currentPageOffset)); Array.Copy(page, currentPageOffset, buffer, offset + bytesRead, bytesToCopy); bytesRemaining -= bytesToCopy; this.position += (uint)bytesToCopy; bytesRead += bytesToCopy; } return bytesRead; } public override int ReadByte() { if (this.position >= this.Length) return -1; uint currentPage = this.position / this.root.Pages.PageSize; uint currentPageOffset = this.position % this.root.Pages.PageSize; var page = this.root.Pages.GetPage(this.streamInfo.Pages[currentPage]); this.position++; return page[currentPageOffset]; } public override long Seek(long offset, SeekOrigin origin) { switch (origin) { case SeekOrigin.Begin: this.position = (uint)offset; break; case SeekOrigin.Current: this.position = (uint)((long)this.position + offset); break; case SeekOrigin.End: this.position = (uint)(this.Length + offset); break; } return this.position; } public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override void WriteByte(byte value) => throw new NotSupportedException(); } private sealed class RootIndex { private BinaryReader reader; private List<StreamInfo> streams = new List<StreamInfo>(); private StreamInfo rootStreamInfo; private StreamInfo rootPageListStreamInfo; private uint freePageMapIndex; public RootIndex(Stream stream) { this.reader = new BinaryReader(stream); this.Initialize(); } public PagedFile Pages { get; private set; } public Stream OpenRead(int streamIndex) { var streamInfo = this.streams[streamIndex]; return new PdbStream(this, streamInfo); } public void Close(bool leaveStreamOpen) { if (!leaveStreamOpen) this.reader.Dispose(); } private void Initialize() { this.reader.BaseStream.Position = 0x20; var pageSize = this.reader.ReadUInt32(); var pageFlags = this.reader.ReadUInt32(); var pageCount = this.reader.ReadUInt32(); var rootSize = this.reader.ReadUInt32(); this.reader.ReadUInt32(); // skip reserved this.Pages = new PagedFile(this.reader.BaseStream, pageSize, pageCount); this.freePageMapIndex = pageFlags; // Calculate the number of pages needed to store the root data int rootPageCount = (int)(rootSize / pageSize); if ((rootSize % pageSize) != 0) rootPageCount++; // Calculate the number of pages needed to store the list of pages int rootIndexPages = (rootPageCount * 4) / (int)pageSize; if (((rootPageCount * 4) % (int)pageSize) != 0) rootIndexPages++; // Read the page indices of the pages that contain the root pages var rootIndices = new List<uint>(rootIndexPages); for (int i = 0; i < rootIndexPages; i++) rootIndices.Add(this.reader.ReadUInt32()); // Read the free page map this.reader.BaseStream.Position = pageFlags * pageSize; this.Pages.InitializeFreePageList(this.reader.ReadBytes((int)pageSize)); this.rootPageListStreamInfo = new StreamInfo(rootIndices.ToArray(), (uint)rootPageCount * 4); // Finally actually read the root indices themselves var rootPages = new List<uint>(rootPageCount); using (var rootPageListStream = new PdbStream(this, this.rootPageListStreamInfo)) using (var pageReader = new BinaryReader(rootPageListStream)) { for (int i = 0; i < rootPageCount; i++) rootPages.Add(pageReader.ReadUInt32()); } this.rootStreamInfo = new StreamInfo(rootPages.ToArray(), rootSize); using (var rootStream = new PdbStream(this, this.rootStreamInfo)) { var rootReader = new BinaryReader(rootStream); uint streamCount = rootReader.ReadUInt32(); var streamLengths = new uint[streamCount]; for (int i = 0; i < streamLengths.Length; i++) streamLengths[i] = rootReader.ReadUInt32(); var streamPages = new uint[streamCount][]; for (int i = 0; i < streamPages.Length; i++) { if (streamLengths[i] > 0 && streamLengths[i] < int.MaxValue) { uint streamLengthInPages = streamLengths[i] / pageSize; if ((streamLengths[i] % pageSize) != 0) streamLengthInPages++; streamPages[i] = new uint[streamLengthInPages]; for (int j = 0; j < streamPages[i].Length; j++) streamPages[i][j] = rootReader.ReadUInt32(); } } for (int i = 0; i < streamLengths.Length; i++) { this.streams.Add( new StreamInfo(streamPages[i], streamLengths[i]) ); } } } } private sealed class StreamInfo { private uint[] pages; private uint length; public StreamInfo(uint[] pages, uint length, bool dirty = false) { this.pages = pages; this.length = length; this.IsDirty = dirty; } public uint[] Pages { get => this.pages; set { if (this.pages != value) { this.pages = value; this.IsDirty = true; } } } public uint Length { get => this.length; set { if (this.length != value) { this.length = value; this.IsDirty = true; } } } public bool IsDirty { get; private set; } } } -
RE: Symbol Server id issueposted in Support
Hi @it_9582 ,
It's certainly possible; there's a few hundred lines of code that make up the
MicrosoftPdbFileclass, so I don't know which parts to share with you. Of course I'm happy to share it all if you'd like.Since you mentioned your colleague was able to read the file, perhaps you can share what you did, and I can see how it compares to our code?
Thanks,
Alana -
RE: Symbol Server id issueposted in Support
Hi @it_9582 ,
If there's an error reading the file using
GetMetadataReader, then we load it using theMicrosoftPdbFileclass that we wrote. So, I'm guessing that's what's causing the wrong information?Anyway, let me share you the full code for the
PortablePdbFileclass. I summarized it before, but you can see the full context of what we're doing any why.using System.Collections.Immutable; using System.IO; using System.Reflection.Metadata; namespace Inedo.ProGet.Symbols; public sealed class PortablePdbFile : IPdbFile { private readonly MetadataReader metadataReader; private PortablePdbFile(MetadataReader metadataReader) => this.metadataReader = metadataReader; // visual studio always treats this value like a guid, despite the portable pdb spec public ImmutableArray<byte> Id => this.metadataReader.DebugMetadataHeader.Id.RemoveRange(16, 4); // not really age, but actually last 4 bytes of id - ignored by visual studio uint IPdbFile.Age => BitConverter.ToUInt32(this.metadataReader.DebugMetadataHeader.Id.ToArray(), 16); bool IPdbFile.IsPortable => true; public IEnumerable<string> GetSourceFileNames() { foreach (var docHandle in this.metadataReader.Documents) { if (!docHandle.IsNil) { var doc = this.metadataReader.GetDocument(docHandle); yield return this.metadataReader.GetString(doc.Name); } } } public static PortablePdbFile Load(Stream source) { if (source == null) throw new ArgumentNullException(nameof(source)); try { var provider = MetadataReaderProvider.FromPortablePdbStream(source, MetadataStreamOptions.LeaveOpen); var reader = provider.GetMetadataReader(); if (reader.MetadataKind != MetadataKind.Ecma335) return null; return new PortablePdbFile(reader); } catch { return null; } } void IDisposable.Dispose() { } } -
RE: Proget 25.x and Azure PostGresposted in Support
From the cybersecurity perspective, it's fine to leave it as root since the core process is run by the non-root user
postgresinside of the container. You're never exposing a network service while the containerized process has root privileges.Here is more information on this if you're curious:
https://stackoverflow.com/questions/73672857/how-to-run-postgres-in-docker-as-non-root-userAs you can see in that link, it's technically possible to configure as non-root, but it requires more effort and doesn't really get you any benefit.
As for load-testing and restarting, it really depends on the hardware and similar factors. Keep in mind that InedoDB is simply the postgresql container image with some minor configuration tweaks/changes. So any question you ask about InedoDB you can really ask about postgresql as well.
As for using an external PostgreSQL server, the only information we have at this time is in the link I sent you before. You really need to be an expert on PostgreSQL if you wish to run your own server.
Thanks,
Alana