Changelog
What shipped, and when.
The website, the documentation and the platform work that is finished, newest first. Nothing here describes a feature that is only designed.
Filter
September 2026
- added
Support tickets from your dashboard
Raise a ticket, read the thread and reply from the dashboard. Tickets are ours, in our own database, with no third party involved.
You can raise a support ticket from your dashboard. Open Support, write what is happening, pick a severity and attach the service it is about. The thread is on the same screen: replies appear on it, you can add to it, and you can close it when you are done.
The entry points are where you notice the problem. Your organisation home has one, and so does each service's Overview tab — a ticket raised from a service arrives with that service already attached, so nobody has to ask you which one.
Email still works and always will. It is the same queue.
The ticket system is ours. It is a table in the database that already holds your account and your services, in the same place, under the same access rules. No third party is involved, so the subprocessor register is unchanged and your ticket does not leave infrastructure we operate in the EU.
Messages cannot be edited or deleted, by you or by us. The database refuses it. A support conversation is often the only place we told you something, and a record you can tidy afterwards is not a record. The consequence is published rather than hidden: section 8 of the Privacy Policy says that nothing deletes a support conversation today, and why.
Two things are not here. You cannot attach a file yet — paste the relevant lines into the message instead, and a request that names an attachment is refused with that reason rather than accepted and dropped. And no first-response target is published, because the Service Level Agreement commits to none. Severity sets the order we work in.
This is the entry the support hours announcement of 21 September said would arrive when ticketing served.
- changed
Support hours withdrawn, and support opened at any time
Balta publishes no support hours. Support accepts email at any time. The published first-response targets are withdrawn because the SLA commits to none.
Balta publishes no support hours. Write to us at any hour and we will read it.
Every service is monitored around the clock.
Support is by email today. Ticketing from your dashboard is decided and not built, and this entry will not claim a channel you cannot use yet. It is announced here when it serves.
This replaces the hours announced on 18 September 2026: Monday to Friday, 09:00 to 18:00 Vilnius time. That entry stays where it is, because it was true when it was written and a changelog you can edit afterwards is not a record.
The first-response targets published with those hours are withdrawn. They were written in business hours — "4 business hours", "1 business day" — and measured against a clock we have stopped publishing. We do not publish a response-time target unless the Service Level Agreement commits to it, and it now commits to none. Section 8 of that agreement says so directly, and the change is with our lawyers along with the rest of the document.
Severity still decides the order we work in, and the severity definitions are unchanged. They are on /legal/sla and in your dashboard when you write to us.
Nothing about monitoring changed. The platform sweeps every running service on a five-minute cycle for backup-chain gaps, stalled write-ahead log archiving and failed restore verifications, and it has done so continuously since it was built. What changed is that support is no longer described as something with an edge to fall off.
- fixed
A service's IP allowlist is applied, at two enforcement points
The per-service IP allowlist is applied at the host firewall and in PostgreSQL's own authentication. Until today the rules were stored and nothing carried them to a host.
Until today, a service's IP allowlist filtered nothing. Rules you saved were validated, stored, shown back to you and recorded in your audit log, and no code path carried one to a database host. Nothing was refused because of a rule you set.
We said so on the pages that described it, which is the least we could do and not much. The entry that matters is this one: it is applied now.
What changed
A rule is applied at two places, and both together:
- The host firewall. A packet from an address that is not on your list is dropped before PostgreSQL sees it, so it costs no connection slot and no handshake. From the client it looks like a timeout.
pg_hba.conf. PostgreSQL's own authentication, authoritative for anything that reaches the server and unaffected by a firewall somebody has flushed. A connection refused here saysno pg_hba.conf entry for host.
Neither is enough on its own. A firewall is bypassed by anything already inside the host's network;
pg_hba.confalone lets an address you excluded spend a connection slot before it is refused.Only TLS connections are described in either. A connection without TLS matches nothing.
What you should check
An empty list accepts nothing — your own application included. That was always what the list meant and it is now what it does.
Every service created from today starts with an explicit Allow all addresses rule. It is in the list, it has a label, and you replace it with your own ranges when you are ready. If your list is open, the list says so; there is no hidden default.
If you had saved rules before today expecting them to do nothing, they do something now. Open the Connect tab and read your list.
What it does not cover
The data browser and the SQL editor reach your database from inside Balta, so an address you have not listed can still be used to open one. Roles and capabilities decide who may, and every statement is in your audit log. Turn the editor off for a service on its Configuration tab if that is not acceptable.
How we know
An unlisted address is refused and a listed one connects, measured on a prepared machine rather than against a stand-in:
no pg_hba.conf entry for hostfrom one enforcement point, a dropped packet from the other, and the same address connecting again once it is back on the list — so the refusals are the list and not a service that stopped working.- added
Falkenstein named as the first location, and four subprocessors announced
The first location is Falkenstein, Germany. Hetzner, Scaleway, Postmark and Sentry are on the subprocessor register, announced before any of them processes customer data.
The first location is Falkenstein, Germany, in Hetzner's FSN1 data centre park. It is listed on /locations as planned. A location takes its first customers once three hosts are running there, so any one of them can be rebuilt on the other two. Backups go to two repositories: the primary at Hetzner in Germany and the second at Scaleway in France.
Four subprocessors are announced on the subprocessor register:
- Hetzner, for the dedicated servers that run customer databases and the primary backup repository, in Germany.
- Scaleway, for the second backup repository, in France.
- Postmark, for transactional email: sign-in codes, password resets, alerts and billing notices. Postmark processes in the United States, under the EU-U.S. Data Privacy Framework and Standard Contractual Clauses.
- Sentry, for error tracking. Error reports are stored in its EU region, in Frankfurt; Sentry keeps its account and organisation settings in the United States.
None of the four processes customer data yet. This entry, and the register, are the notice.
Support hours are published: Monday to Friday, 09:00 to 18:00 Vilnius time, with first-response targets by severity on /contact and in the Service Level Agreement. Infrastructure monitoring and operator on-call run around the clock regardless.
The legal documents are with our lawyers. The Privacy Policy, Cookie Notice, Service Level Agreement and Subprocessor Register are in legal review, and each page says so at the top.
- security
Per-service certificates are a design, not a control that is running
We described a certificate per service as a fact on eight surfaces. No certificate authority exists in either binary. The wording is corrected everywhere.
Amended 22 September 2026. Parts of what follows were true when this was published and are not now. The control plane has an ACME client, the host agent generates an endpoint key and asks for a certificate over it, and a connection to a host bed service verifies against the system trust store in five client libraries. What also changed is the authority: the private CA described below is withdrawn, there is no bundle to download, and TLS and certificate verification is the current instruction. What has still not happened is an issuance from a public authority to a production endpoint, so this entry is amended rather than retracted.
A certificate per service, from our own private CA, was written in the present tense on the homepage,
/product/security,/trust,/pricing,/solutions/agencies,/solutions/saas,/product/managed-postgres, two documentation pages, our privacy notice's Article 32 summary, and a changelog entry announcing it as shipped.None of it is running, and the check is not subtle:
- there is no certificate authority, no signing service and no ACME server in the control plane;
- the word
certificatedoes not appear once in the host agent, so nothing generates a key, nothing writes one to a service's directory and nothing configures PostgreSQL to present one; tls.ca_bundle_urlhas no producer, which/pricingand the connection documentation already said — and naming only the missing bundle made the gap read as one step outstanding rather than as the whole control.
Transport is still encrypted. What is not true today is the identity half: the certificate a service presents is not one issued to that service alone by a CA of ours, because no such CA exists.
verify-fullis still the mode to use, and it is not yet a mode you can use against us.This is the same correction as encryption at rest and the per-service IP allowlist, and it is the same mistake: a design was written down, the copy was written from the design, and nothing compared either against the code. The comparison is now a test —
apps/web/app/copy-rules.test.tsreads both Go modules for a certificate issuer and fails if a customer-facing surface names the control without saying it is not running. It fails in the other direction too: on the day an issuer appears, it names the copy that has become needlessly hedged.- added
Documentation, blog and changelog published
The documentation tree, the first six engineering posts and this changelog are live, with an RSS feed at /changelog/rss.xml.
The documentation covers getting started, connecting, managing databases and roles, backups, migration, monitoring, security, billing and reference. Some pages are outlines rather than finished prose, and each of those says so at the top rather than reading as though it were complete.
The blog carries six technical posts written from the architecture documents: what a memory allocation actually includes, how restore verification works and why its cadence follows database size, why there is no wildcard certificate, how the pooled and direct endpoints differ, why every location holds a host's worth of unallocated capacity, and why the single-node architecture is stated on the pricing page rather than buried.
This changelog is grouped by month and filterable by type. The feed is at
/changelog/rss.xml.- added
Privacy Policy, Cookie Notice, SLA and Subprocessor Register published as drafts
Four legal documents are published in full, each carrying a banner saying it has not been reviewed by a lawyer.
The Privacy Policy, Cookie Notice, Service Level Agreement and Subprocessor Register are published. They were written from our own architecture and data flows rather than from a template, and every one of them carries a banner saying it is a first-version draft that a lawyer has not yet reviewed. The banner stays until a lawyer signs the document off.
The Terms of Service, Data Processing Agreement and Acceptable Use Policy have routes and no text. Those three allocate liability between two companies, and a template-derived draft is worse than an empty page because it reads as though someone had thought about it.
The subprocessor register is empty because no subprocessor is contracted. It renders that fact in words. An empty table with a header row would read as "we checked and there are none", which is a different and untrue statement.
- added
Design system, tokens and component gallery
One token source, one component library, and a gallery that renders every component in every state including loading, empty, error and permission-denied.
Colour, type, spacing, radius and motion are defined once in
packages/tokensand consumed by the website and both dashboards. Contrast is asserted by a test against the token values rather than checked by eye, and two status colours are restricted to dots, icons and chart marks because they fall below 4.5:1 as small text on their own tint.Every component appears in the internal gallery in every state it can be in. A component with no empty state is not finished, so the gallery is where that gets caught.
Motion durations are tokens, which means
prefers-reduced-motion: reducesets them to zero in one place instead of in every component.- added
security.txt published
A researcher looking for somewhere to report a vulnerability now finds one at /.well-known/security.txt.
/.well-known/security.txtis served, and/security.txtredirects to it. It names the contact address, the preferred language and the disclosure policy.It costs nothing and it is the first thing a security researcher looks for. The
Encryptionfield is omitted rather than pointing at a key that does not exist yet — a link to a missing key is worse than no link, because it wastes the time of the one person trying to help.
August 2026
- fixed
Recovery reserve formula corrected before any hardware was ordered
The capacity check reserved the wrong number and would have passed a location that could not survive losing a host.
Every location holds unallocated capacity so that the services on a failed host can be rebuilt on the hosts that remain. The formula that sized it counted the free capacity on the host being lost as available to absorb that host's own workload.
The worked example that found it: three hosts, 100 GB allocatable each, 74 GB allocated on each. The old check reserved 74 GB, reported 4 GB sellable, and showed green. Losing one host leaves 26 + 26 = 52 GB free on the survivors to re-home a 74 GB workload. It does not fit.
The reserve is now the allocatable capacity of the largest host in the location, evaluated independently for vCPU, RAM, storage and IOPS, because the largest host may be a different host in each dimension. Aggregate capacity is not sufficient on its own either: three 30 GB services on a failed host do not fit onto two survivors with 50 GB free each, even though the totals say they should, so a per-service fit check runs as well.
This was caught in review rather than during an incident, which is the only acceptable time to find it.
- changed
Per-service certificates from a platform-operated private CA — the design
The certificate design we committed to in August, corrected twice since. The private CA it describes was withdrawn on 22 September 2026 and no CA bundle will be published.
Amended 22 September 2026: the design below is withdrawn. There is no private certificate authority and no CA bundle to download. A service endpoint presents a publicly trusted certificate instead, and a client verifies it against the trust store it already has:
sslmode=verify-full&sslrootcert=systemon libpq and onpgx,sslfactory=org.postgresql.ssl.DefaultJavaSSLFactoryon pgjdbc, andrejectUnauthorizedwithservernameon node-postgres. See TLS and certificate verification. The entry is kept because it is what we told people in August; the rest of it is left as it stood.This entry originally announced a shipped feature. It said "each service now gets its own leaf certificate", and no such thing existed when it was written: there was no certificate authority in either binary, nothing signed a leaf, no signing service ran, and the word
certificatedid not appear anywhere in the host agent. That wording was corrected on 8 September 2026, here and everywhere else it had reached. What follows is the August design, stated as a design and left as it stood.- The root is generated offline, kept offline, and signs intermediates only. It is never present on a production host.
- The intermediate is held by the control-plane signing service, which runs as an ACME server on the management network. It is never present on a database host.
- The private key is generated on the host by the host agent, inside the service's own directory, owned by the service user at mode 0400. It is not transmitted anywhere, including to the control plane.
- Certificates are short-lived and renewed automatically well before expiry, because PostgreSQL clients rarely check revocation lists and a short lifetime is the control that actually works.
Each leaf would carry one subject alternative name, that service's endpoint hostname, so it could not be used to impersonate another service. Customers would download our CA bundle and connect with
sslmode=verify-full; the bundle would carry the current and the next root with an overlap period, so a root rotation never required a coordinated change on your side. That is the half the note above withdraws: there is no bundle, and none is coming.Stated plainly, and for the design rather than for anything running: compromising one database runtime would yield that service's key only. Compromising a host at root would yield the keys of the services on that host. Neither reaches another customer's service on another host, and the intermediate is on neither.
- deprecated
Per-location wildcard certificate design withdrawn
One private key shared by every database in a location is one compromise away from impersonating all of them. The design is gone.
An earlier revision specified one wildcard certificate per location, with the private key distributed to every host and readable by each service user.
That means a single compromised database runtime yields a key capable of impersonating every database in that location — precisely the blast radius the isolation model exists to prevent. The design is withdrawn and does not appear in any build.
What replaces it is in the entry below.
- changed
CPU wording corrected — a proportional weight is not a reservation
Earlier material described the cgroup CPU weight as guaranteeing a floor. It does not, and the wording has been replaced everywhere.
CPUWeightsets the proportional share a cgroup receives when the CPU is contended. It does not reserve capacity and it does not prevent contention. Describing it as a floor was wrong, and that wording is gone from every document.Where the predictability actually comes from, in the order it applies:
- The control plane knows every allocation on every host and reserves capacity before it provisions anything.
- The sum of service vCPU on a host does not exceed the CPU that exists. The allocation ratio is 1.0 at launch.
CPUQuotacaps each service at its plan's vCPU. A 2 vCPU service gets two CPU-seconds per second and no more. There are no burst credits.- CPU for the operating system, the agent, monitoring, backups and maintenance is excluded from allocatable capacity before any of that is computed.
CPUWeightis still configured. Its job is narrow: deciding how residual contention is resolved when host overhead spikes. If the allocation ratio is ever raised above 1.0, the claim we publish changes at the same time.