Skip to content

feat: deprecate embedded postgres ahead of v4 - #3610

Merged
dkrizan merged 10 commits into
mainfrom
dkrizan/docker-slim-image
Jul 28, 2026
Merged

feat: deprecate embedded postgres ahead of v4#3610
dkrizan merged 10 commits into
mainfrom
dkrizan/docker-slim-image

Conversation

@dkrizan

@dkrizan dkrizan commented Apr 17, 2026

Copy link
Copy Markdown
Member

What

Two changes, both preparing for v4 dropping the bundled postgres from tolgee/tolgee:

  1. Deprecation warning — the app logs a WARN on startup when it's actually running the bundled postgres, pointing at the external-database docs.
  2. Internal slim imageDockerfile.slim + the dockerSlim Gradle task. Not published. Built on demand by the EE image build and by the smoke test.

Why

In v4, tolgee/tolgee loses the embedded postgres. Self-hosters running the bundled database have to move to an external one before upgrading.

They can already do that today on the current image by setting postgres-autostart.enabled=false (this is what the external-database docs section has always described). So the warning gives them a full release cycle of notice, on every boot, for a migration that needs nothing new from us.

The slim image is deliberately not published: a public :slim tag would only advertise a name that goes obsolete the moment v4 makes it the default, and it buys users nothing they can't already do. It exists so the internal EE image can drop the postgres layers now.

Notes for review

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a slim Docker image variant with Alpine Java 21, OpenTelemetry support, and health checks. Gradle tasks and release automation build and publish it, CI smoke-tests it against PostgreSQL, and startup logging warns when embedded PostgreSQL remains enabled.

Changes

Slim image and release flow

Layer / File(s) Summary
Slim Dockerfile definition
docker/app/Dockerfile.slim
Defines the Alpine Java 21 runtime, application files, required OpenTelemetry agent, startup entrypoint, and /actuator/health check.
Gradle build and publish tasks
gradle/docker.gradle
Adds local and multi-platform slim image build tasks with OpenTelemetry configuration, cache support, and versioned or floating slim tags.
CI smoke test and release integration
.github/workflows/test.yml, .github/workflows/release.yml
Builds and runs the slim image against PostgreSQL, polls health until UP, reports logs on failure, gates everything-passed, and publishes release and latest slim tags.
Embedded PostgreSQL warning
backend/app/src/main/kotlin/io/tolgee/commandLineRunners/EmbeddedPostgresDeprecationCommandLineRunner.kt
Logs a warning when embedded PostgreSQL autostart is configured in EMBEDDED mode.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: jancizmar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main theme: deprecating embedded PostgreSQL and steering users toward the new slim image path.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dkrizan/docker-slim-image

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
gradle/docker.gradle (1)

95-123: Consider extracting shared publish logic to reduce duplication.

dockerPublishSlim is nearly identical to dockerPublish — the only differences are the tag mapping and the -f Dockerfile.slim flag. If a third variant is ever added, the duplication will compound. A small helper (e.g., a closure taking dockerfile and a tagResolver) would DRY this up without changing behavior.

♻️ Sketch
def buildxPublish = { String dockerfile, Closure<String> tagResolver ->
    def version = System.getenv('VERSION') ?: 'latest'
    def imageName = System.getenv('DOCKER_IMAGE') ?: 'tolgee/tolgee'
    def tag = tagResolver(imageName, version)
    def cacheFrom = System.getenv('DOCKER_CACHE_FROM')
    def cacheTo = System.getenv('DOCKER_CACHE_TO')

    def cmd = ["docker", "buildx", "build", "."]
    if (dockerfile) cmd += ["-f", dockerfile]
    cmd += ["-t", tag,
            "--build-arg", "OTEL_AGENT_VERSION=${opentelemetryJavaagentVersion}",
            "--platform", "linux/arm64,linux/amd64",
            "--push"]
    if (cacheFrom) cmd += ["--cache-from", cacheFrom]
    if (cacheTo)   cmd += ["--cache-to", cacheTo]

    exec { workingDir dockerPath; commandLine cmd }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gradle/docker.gradle` around lines 95 - 123, dockerPublishSlim duplicates
dockerPublish; extract the shared logic into a reusable closure (e.g.,
buildxPublish) that accepts a dockerfile name and a tag-resolver closure, move
the environment reads (VERSION, DOCKER_IMAGE, DOCKER_CACHE_FROM,
DOCKER_CACHE_TO) and common args (buildx build, OTEL_AGENT_VERSION build-arg,
platforms, push) into that closure, then replace the dockerPublishSlim task body
to call buildxPublish("Dockerfile.slim", { imageName, version -> /* slim tag
logic */ }) and likewise call it from dockerPublish with the default Dockerfile
and tag resolver, preserving the existing tag mapping and cache-from/cache-to
handling.
docker/app/Dockerfile.slim (1)

34-35: Pin the OTEL agent JAR by checksum (optional hardening).

The agent JAR is fetched over HTTPS from GitHub Releases without integrity verification. A checksum check (the project publishes .sha256/.asc alongside the jar) protects the image build from upstream tampering or transient corruption. Same concern applies to the non-slim Dockerfile; feel free to track separately and apply uniformly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker/app/Dockerfile.slim` around lines 34 - 35, The Dockerfile.slim RUN
step that downloads opentelemetry-javaagent.jar using OTEL_AGENT_VERSION must be
hardened by verifying the artifact checksum: fetch the corresponding .sha256 (or
.sha256sum/.asc) published alongside the jar (using the same
OTEL_AGENT_VERSION), validate the downloaded /app/opentelemetry-javaagent.jar
with sha256sum (or gpg verify if using .asc), and fail the build if the
verification fails; update the RUN instruction that does wget to also wget the
checksum (or signature) and perform the verification before keeping the jar, and
apply the same change to the non-slim Dockerfile equivalent so both images are
protected from tampered or corrupted releases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker/app/Dockerfile.slim`:
- Around line 1-3: The Dockerfile runs as root by default; create and switch to
a non-root user (e.g., user name "tolgee") by adding a user/group creation step,
ensure ownership of runtime directories like /data is changed (chown) so the new
user can write, copy application files as that non-root user (or adjust
ownership after copy), and set USER tolgee before the ENTRYPOINT to run the
container non-root; update any steps that assume root accordingly.

---

Nitpick comments:
In `@docker/app/Dockerfile.slim`:
- Around line 34-35: The Dockerfile.slim RUN step that downloads
opentelemetry-javaagent.jar using OTEL_AGENT_VERSION must be hardened by
verifying the artifact checksum: fetch the corresponding .sha256 (or
.sha256sum/.asc) published alongside the jar (using the same
OTEL_AGENT_VERSION), validate the downloaded /app/opentelemetry-javaagent.jar
with sha256sum (or gpg verify if using .asc), and fail the build if the
verification fails; update the RUN instruction that does wget to also wget the
checksum (or signature) and perform the verification before keeping the jar, and
apply the same change to the non-slim Dockerfile equivalent so both images are
protected from tampered or corrupted releases.

In `@gradle/docker.gradle`:
- Around line 95-123: dockerPublishSlim duplicates dockerPublish; extract the
shared logic into a reusable closure (e.g., buildxPublish) that accepts a
dockerfile name and a tag-resolver closure, move the environment reads (VERSION,
DOCKER_IMAGE, DOCKER_CACHE_FROM, DOCKER_CACHE_TO) and common args (buildx build,
OTEL_AGENT_VERSION build-arg, platforms, push) into that closure, then replace
the dockerPublishSlim task body to call buildxPublish("Dockerfile.slim", {
imageName, version -> /* slim tag logic */ }) and likewise call it from
dockerPublish with the default Dockerfile and tag resolver, preserving the
existing tag mapping and cache-from/cache-to handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bae4c2b4-b78c-46bb-821a-5f6e662feb93

📥 Commits

Reviewing files that changed from the base of the PR and between ff89c7b and 615ddf6.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • docker/app/Dockerfile.slim
  • gradle/docker.gradle

Comment thread docker/app/Dockerfile.slim
@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 30 days with no activity.

@github-actions github-actions Bot added the stale label May 18, 2026
dkrizan and others added 2 commits May 26, 2026 08:51
The existing `tolgee/tolgee` image is based on `postgres` to support
`postgres-autostart.mode: EMBEDDED`. For production deployments against an
external database, the postgres server binaries and their transitive OS
packages aren't used at runtime.

Add a slim variant built from `eclipse-temurin:21-jre-alpine` that carries
only what the application needs. Published alongside the existing image:

  - tolgee/tolgee:slim          (floating, tracks latest)
  - tolgee/tolgee:<version>-slim

The slim image sets `TOLGEE_POSTGRES_AUTOSTART_ENABLED=false` by default
since embedded postgres isn't available in this variant; it must be run
against an external database.

Revives the approach from PR #3193 (stale). Current Dockerfile and default
`tolgee/tolgee` tag are unchanged — this is purely additive.

Co-authored-by: Matthew Sekirin <matthew.sekirin@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match #3676's policy for the default image — relying on the registry
layer cache pins the apk layer to the previously published image and
masks CVE fixes. Drop DOCKER_CACHE_FROM from both dockerPublishSlim
invocations so each release re-resolves apk packages.
@dkrizan
dkrizan force-pushed the dkrizan/docker-slim-image branch from 615ddf6 to 448a8be Compare May 27, 2026 07:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docker/app/Dockerfile.slim (1)

1-3: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Run the slim image as non-root before startup.

This Dockerfile still defaults to root at runtime. Please create a dedicated user, fix ownership for /app and /data, and set USER before ENTRYPOINT.

Suggested patch
 FROM eclipse-temurin:21-jre-alpine
 
 RUN apk --no-cache add bash
+RUN addgroup -S tolgee \
+    && adduser -S -G tolgee tolgee \
+    && mkdir -p /app /data \
+    && chown -R tolgee:tolgee /app /data
@@
-COPY BOOT-INF/lib /app/lib
-COPY META-INF /app/META-INF
-COPY BOOT-INF/classes /app
-COPY --chmod=755 cmd.sh /app
+COPY --chown=tolgee:tolgee BOOT-INF/lib /app/lib
+COPY --chown=tolgee:tolgee META-INF /app/META-INF
+COPY --chown=tolgee:tolgee BOOT-INF/classes /app
+COPY --chown=tolgee:tolgee --chmod=755 cmd.sh /app
@@
 # Define the startup command
+USER tolgee
 ENTRYPOINT ["/app/cmd.sh"]

Also applies to: 13-13, 23-27, 41-42

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker/app/Dockerfile.slim` around lines 1 - 3, Create a non-root runtime
user and ensure app directories are owned by it: add steps after the base image
and package install (FROM eclipse-temurin:21-jre-alpine and RUN apk --no-cache
add bash) to create a dedicated user/group (e.g., appuser), create or ensure
/app and /data exist, chown them to that user, and finally set USER appuser
before the ENTRYPOINT so the container runs non-root; apply the same pattern to
the other Dockerfile variants referenced (lines 13, 23-27, 41-42).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@docker/app/Dockerfile.slim`:
- Around line 1-3: Create a non-root runtime user and ensure app directories are
owned by it: add steps after the base image and package install (FROM
eclipse-temurin:21-jre-alpine and RUN apk --no-cache add bash) to create a
dedicated user/group (e.g., appuser), create or ensure /app and /data exist,
chown them to that user, and finally set USER appuser before the ENTRYPOINT so
the container runs non-root; apply the same pattern to the other Dockerfile
variants referenced (lines 13, 23-27, 41-42).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 12f35f0d-701b-4dfd-a25c-14ebe36dd39b

📥 Commits

Reviewing files that changed from the base of the PR and between 615ddf6 and 448a8be.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • docker/app/Dockerfile.slim
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release.yml

@github-actions github-actions Bot removed the stale label May 28, 2026
@dkrizan
dkrizan marked this pull request as draft May 29, 2026 14:34
Build the slim image, run it against a real postgres service container,
and wait for /actuator/health to report UP. Catches regressions where a
change works on the default postgres-based image but breaks on slim
(missing native deps, JRE-distribution divergence, autostart assumptions
in startup code, etc.).
@dkrizan
dkrizan marked this pull request as ready for review June 5, 2026 13:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/test.yml:
- Around line 545-548: Replace the fragile sed-based JSON parsing that sets the
status variable from the curl output with a jq-based extraction so the top-level
"status" is returned deterministically; change the command that assigns status
(the curl ... | sed ... pipeline) to use jq -r '.status' (or equivalent jq
expression) to pull the root status, preserving the existing loop and condition
that checks if "$status" = "UP".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc708a9f-ada6-40ad-9741-a538ebaddd82

📥 Commits

Reviewing files that changed from the base of the PR and between 448a8be and 9e5051f.

📒 Files selected for processing (1)
  • .github/workflows/test.yml

Comment thread .github/workflows/test.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/test.yml:
- Around line 499-503: The docker-slim-smoke job currently inherits default
GITHUB_TOKEN permissions; update the job definition for docker-slim-smoke to
declare explicit least-privilege permissions (e.g., add a permissions block with
contents: read) so the job only has read access to repository contents for
checkout/build/run and does not inherit broader default scopes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29c59737-c937-41f0-a448-093000e83c80

📥 Commits

Reviewing files that changed from the base of the PR and between 448a8be and 52ecf79.

📒 Files selected for processing (1)
  • .github/workflows/test.yml

Comment thread .github/workflows/test.yml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Anty0
Anty0 previously approved these changes Jun 25, 2026
Comment thread .github/workflows/test.yml Outdated
Comment thread docker/app/Dockerfile.slim
Comment thread gradle/docker.gradle Outdated
The bundled PostgreSQL will be removed once the slim image becomes the
default tolgee/tolgee image. Warn operators running the embedded server
so they can migrate to an external database before that change.
…rning

The slim image is no longer published to Docker Hub. Self-hosters migrate
by pointing the current image at an external database, which the fat image
already supports, so a public slim tag would only advertise a name that
becomes obsolete once v4 drops the bundled postgres.

The image is still built on demand by the EE image build and the smoke
test, so dockerSlim stays and dockerPublishSlim goes.
@dkrizan dkrizan changed the title feat: add slim Docker image variant without embedded postgres feat: deprecate embedded postgres ahead of v4 Jul 16, 2026
dkrizan added 3 commits July 16, 2026 16:07
Only the EMBEDDED mode goes away in v4. DOCKER mode stays, as it is how
Tolgee starts postgres when running with Java, so the deprecation is
scoped to the mode rather than the whole postgres-autostart config.
The notices linked the external database section, which sets up an empty
database on postgres:15. That is the wrong target for the people the notices
reach: a bundled database is postgres:13, and postgres:15 refuses to start on
its files.
Postgres 13 went end of life in November 2025. The backend test suite already
runs against 16.3 via the docker autostart runner, so 17 is a small step from
what is tested today, and it is the version the self hosting docs point at.
@dkrizan
dkrizan force-pushed the dkrizan/docker-slim-image branch from 76ee870 to 9ef40c2 Compare July 17, 2026 10:11
@dkrizan
dkrizan requested a review from Anty0 July 21, 2026 11:58
dkrizan added a commit to tolgee/documentation that referenced this pull request Jul 28, 2026
…ation (#1121)

## What

Two changes:

1. **`platform/self_hosting/running_with_docker.mdx`** — warns that
Tolgee v4 removes the bundled PostgreSQL from the `tolgee/tolgee` image,
and adds a **Migrate from the bundled database** section with the actual
procedure.
2. **`.github/workflows/update-generated-code.yaml`** — the docs codegen
job booted `ghcr.io/tolgee/tolgee-ee` standalone and relied on its
embedded postgres. The EE image is becoming slim (no bundled DB), so the
job now starts a `postgres` service and points the app at it.

## Why

In v4 the bundled postgres goes away, so anyone running it has to move
to an external database first. The good news is that this needs **no
dump and restore**: the bundled server keeps its files in
`data/postgres` on the volume users already mount, which is exactly
where a separate postgres container looks for them. Pin the container to
`postgres:13` and it adopts the directory in place.

**This also fixes a live trap.** The existing external-database example
runs `postgres:15` and mounts `./data/postgres:/var/lib/postgresql/data`
— the same directory a bundled user already has. Copy-pasting it as a
migration fails hard:

```
FATAL:  database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 13,
        which is not compatible with this version 15.17
```

That example is fine for fresh installs and now says it's for fresh
installs.

## Verified end-to-end

Against the real `tolgee/tolgee:v3.212.0` image, not by reasoning:

- Booted with the bundled DB, confirmed `PG_VERSION` is `13` at
`data/postgres`, created a marker translation key via the API.
- `postgres:15` on that directory → exits(1), incompatible-files error
above.
- `postgres:13` on that directory → starts, all 103 tables intact.
- Tolgee with `postgres-autostart.enabled=false` against the external
`postgres:13` → boots, starts no embedded server, original admin
password still works, marker key still present.

## Notes for review

- "Running Tolgee in single container" keeps a caution for now, deleted
when v4 ships.
- Companion PRs: tolgee/tolgee-platform#3610 (startup deprecation
warning) · tolgee/billing#274 (EE image switch).
@dkrizan
dkrizan merged commit 3cef7ad into main Jul 28, 2026
74 of 76 checks passed
@dkrizan
dkrizan deleted the dkrizan/docker-slim-image branch July 28, 2026 14:45
TolgeeMachine added a commit that referenced this pull request Jul 30, 2026
# [3.215.0](v3.214.1...v3.215.0) (2026-07-30)

### Features

* auto-invalidate caches by value shape instead of wiping on startup ([#3813](#3813)) ([7e1db10](7e1db10))
* Community translation v1.1 — Contributors (pitch [#3806](#3806)) ([#3819](#3819)) ([3232c4f](3232c4f))
* deprecate embedded postgres ahead of v4 ([#3610](#3610)) ([3cef7ad](3cef7ad)), closes [tolgee/documentation#1121](tolgee/documentation#1121) [tolgee/billing#274](tolgee/billing#274) [#3193](#3193)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants