Using HTMX to GET component markup and to POST content via Sling's OOTB endpoints
(The name is a pun about the Apache Sling Slingshot sample application, that didn't get much love either.)
Branch-specific quickstart URL pattern:
https://codespaces.new/orx0815/slingslop/tree/<branch>?quickstart=1
One click → a full VS-Code-in-the-browser dev environment, no local setup. See Running the Application for details and the Agent-Smith → review → deploy loop.
-
sling-apps/
Contains the actual applications. Each one consists of two types of modules:- OSGi bundles (e.g.
zengarden.core) for java business-logic in the form of OSGi-Services, -Components and classic Servlets. Also Sling-Models, referenced by templates. Very powerful⚠️ - UI content-packages (e.g. zengarden.ui.apps) for html templates and CSS. Also as little JavaScript as possible.
The
frontentsub-folder has npm-scripts to deal with that: TypeScript compilation, ESLint checks, dependencies and bundling everything into a minified js.
- OSGi bundles (e.g.
-
content-packages/
Holds sample-content (text and images) inJackrabbit FileVault(VLT) zips.
Usually this is used to transfer content between instances, in this case they also become part of the launcher/docker-image.
A complete-package to install everything at once into a running instance.
(Note the three different kinds of packageType :application|content|containerusing different validators.) -
devops/
Production deployment (Ansible → single VPS) and the CONGA config generator. Also home ofdocker-compose.ymlfor a local sandbox with the caching web-proxy in front — see devops/README.md. -
launcher/
The core module responsible for building the runnable Sling application using theslingfeature-maven-plugin. The feature definitions are inlauncher/src/main/features.Includes a launcher dependency BOM module (
launcher/launcher-dependencies) for provided API version alignment with Sling. Dependencies listed there can simple be used, because they are already present in the launcher so do not need to be added to own OSGi bundles. -
doc/ Help coding agents grasp some concepts and conventions
- Java 25 and
- Maven 3.9.12+ for building the project.
- Docker Optional, for running the apps and their web-proxy a in a container
To build the entire slingslop project, run the following command from the root directory:
mvn clean install -DskipITsTo build the Docker image as well, including integration-tests, use:
mvn clean install -Ddocker.skip=falseThis produces the standard image where everything (code under /apps,
/libs and content under /content, /home, /var, …) lives in a single
read-write Segment NodeStore inside the Docker volume /opt/sling/launcher.
That is the original mode and remains the default.
To additionally produce a composite-mode image - where /apps and /libs
are baked into the image as a read-only Segment NodeStore and only the
mutable content sits in the Docker volume - add -Pcomposite-image:
mvn clean install -Ddocker.skip=false -Pcomposite-imageThis runs the base build first, then launcher/src/build/seed_and_bake.sh which boots
the base image once to seed /apps and /libs, freezes that segment store
and bakes it into a derived image tagged
ghcr.io/orx0815/slingslop:snapshot-composite.
The why, how and verification steps live in
docs/composite-nodestore.md.
There are four primary ways to run the application locally:
-
Using the Launcher Script (Recommended for development): The
launcher/launch.shscript starts the application directly without Docker. This is useful for quick development cycles.cd launcher && ./launch.sh
The The CSS Zen Garden can be enjoyed at: http://localhost:8080/content/slingslop/zengarden/home.html
The admin-ui at http://localhost:8080/ -
Using Docker Compose: This method uses the
docker-compose.ymlfile to start the application and the web cache proxy.The
zengardenandwwwvhosts are CONGA-generated (same templates/knobs as prod); generate them once before the firstup(and again after any webcache template/env change):mvn -pl devops/conga -am -DskipTests clean package
# From the root directory docker compose -f devops/docker-compose.yml up --buildYou will also need to add entries to your
/etc/hostsfile as described in devops/README.md to access the different sites like you would in production. -
Start the official Sling Docker image backed by
-
Oak SegmentStore with
docker volume create sling-launcher docker run --rm -p 8080:8080 -v sling-launcher:/opt/sling/launcher apache/sling:13
-
Oak MongoDB DocumentStore with
docker volume create sling-launcher docker run --rm -p 27017:27017 mongo:4.4.6 docker run --rm -p 8081:8080 -v sling-launcher:/opt/sling/launcher apache/sling:13 oak_mongo
and install the ./content-packages/complete/target/slingslop.complete-x.y.z-SNAPSHOT.zip
-
-
Run the prebuilt Slingslop image from GHCR:
docker pull ghcr.io/orx0815/slingslop:latest docker run --rm -p 8080:8080 ghcr.io/orx0815/slingslop:latest
Then open http://localhost:8080/content/slingslop/zengarden/home.html
-
Run the composite-NodeStore variant (immutable
/appsand/libs, only content sits in the volume - seedocs/composite-nodestore.md):docker run --rm -p 8080:8080 \ -v slingslop-content:/opt/sling/launcher \ ghcr.io/orx0815/slingslop:snapshot-composite
The same image, started with the default aggregate, also runs in classic single-store mode - so you can roll back without rebuilding:
docker run --rm -p 8080:8080 \ -v slingslop-volume:/opt/sling/launcher \ ghcr.io/orx0815/slingslop:snapshot \ slingslop_aggregate
-
In the browser (GitHub Codespaces / DevPod / Ona): Use the Open in Codespaces badge. On the GitHub free tier personal accounts get 120 core-hours + 15 GB/month — on the 4-core box this dev container asks for that's ~30 h, comparable to the old Gitpod allowance. The
.devcontainer/provisions Java 25 + Maven, pre-builds the project, and forwards Sling on port 8080 with an in-browser preview. Once the codespace is up:cd launcher && ./launch.sh # → the forwarded 8080 preview serves e.g. # /content/slingslop/zengarden/home.html
The same
devcontainer.jsonis portable: open it in DevPod (open-source, self-host for unlimited hours) or in Ona (the rebranded Gitpod) without changes.For a specific branch, open Codespaces with:
https://codespaces.new/orx0815/slingslop/tree/<branch>?quickstart=1The Agent-Smith → review → deploy loop — fully GitOps, closing the loop the old Gitpod button only hinted at:
- Author — assign a GitHub issue (using the "Agent Smith — new Sling app" template) to the Copilot coding agent. It runs the Agent Smith skill in the cloud and opens a pull request with the new app.
- Review live — open that PR branch in a Codespace (one click from the
PR's Code menu),
./launch.sh, and eyeball the running app in the forwarded preview — no local checkout, "works on my machine" confirmed. - Ship it — merge to
deploy/motorbrot_prod. The CI/CD workflow builds the immutablesha-<commit>image and rolls it onto the VPS (Traefik + Ansible), typically within ~10 minutes.
With a running instance, each bundle or package can be deployed separately. This allows fast code->compile->run development cycles.
Inside an OSGi bundle project, run :
mvn install sling:installInside an content-package project , run :
mvn install wcmio-content-package:installThis one works in both:
mvn -Pfast cq:installWhen working on an application content project ( html/css/js/sightly/jsp/freemarker/thymeleaf ) the fastest is to mount the local filesystem directly into JCR:
mvn sling:fsmountThat way you can save a file inside your IDE and immediately reload your browser.
When done, you should unmount again:
mvn sling:fsmountfollowed by cq:install to have a "normal" state in JCR.
A new app lives next to zengarden under sling-apps/ and follows the same
shape — zengarden is the smallest "Learn Sling" example to copy from. An app is:
- an OSGi bundle (
*.core) for Java business logic — optional; omit it for a pure template/content app, - a
ui.appscontent-package with the HTL scripts under/apps/slingslop/<app>and its page(s) under/content/slingslop/<app>.
The render chain mirrors zengarden: a home node is a page
(sling:resourceType = slingslop/<app>/pages/page) whose html.html delegates to
its jcr:content child, which carries the homepage resourceType that emits the
markup. For a full-featured app let Agent Smith scaffold it (see
docs/agent-skills/create-Sling-app-with-Agent_Smith.md).
If the app follows the /content/slingslop/<name> + /apps/slingslop/<name>
convention, the CONGA tenant is zero-config — CONGA derives contentRoot,
appsRoot, subdomain and homePage from the name, so the whole deployment
registration is three lines. Put it next to the zengarden tenant:
# devops/conga/src/main/environments/prod-motorbrot.yaml
tenants:
- tenant: zengarden
roles: [ public-cached ]
config: { contentRoot: /content/slingslop/zengarden }
- tenant: my-app
roles: [ public-cached ]From those three lines, mvn -pl devops/conga clean package generates — per
environment — the Apache cache/short-URL vhost (webcache/my-app.conf), the
Traefik router (traefik/dynamic/router-my-app.yml) and the outbound Sling
/etc/map short-URL mapping. No proxy/router/mapping file is hand-edited; see
devops/conga/README.md and
docs/conga-config-generation-concept.md.
mvn clean install -DskipITs
cd launcher && ./launch.sh
curl http://localhost:8080/content/slingslop/my-app/home.htmlGotcha — stale local repository.
launch.shkeeps a persisted repository underlauncher/launcher/. If you rebuilt but the page still 404s, the launcher reused the old repo. Wipe it and relaunch:pkill -f 'org.apache.sling.feature.launcher' # stop the running instance rm -rf launcher/launcher # drop the persisted repo cd launcher && ./launch.sh # fresh install
To see it live on the real (Traefik + webcache + Sling) stack — the same Ansible
playbooks prod runs, on a throwaway Vagrant VM — follow
Local testing (Vagrant VM) in the
ops guide. Adding the tenant above is all a new public sub-domain needs: the
GitOps deploy-tenant-edge job ships that generated config (router + vhost +
/etc/map) to a running host without an image rebuild.
Demo credentials — local Vagrant VM, all throwaway, never reuse anywhere real:
Surface Login Sling admin — author UI at editor.slingslop.local, console at/admin/adminEdge basicAuth gate — editor./grafana./logs./traefik.slingslop.locallocaltest/localtestGrafana app login (inside Grafana) admin/adminThese come from
devops/ansible/local/vars.local.yml(+ generatedsecrets.local.yml). A production deploy uses the private vault ondeploy/motorbrot_prodinstead — see devops/README.md.
The sample content under content-packages/ is baked into the image so the
demo apps run out of the box. Real / production content should not live in
the image — it is authored, grows and changes independently of code, and
belongs in the writable content volume (and, at scale, is replicated to publish
instances via Sling Content Distribution — see
devops/README.md).
A tenant supports this directly: its contentRoot can point anywhere, so the
same app can serve different content trees per deployment — e.g. a sling-matrix
app rooted at /content/realProdContent/anothermatrix on one host and at
/content/sling-matrix on another. Ops adds such a tenant at deploy time (router
- vhost +
/etc/map) without a new app or a code change — the mapping is installed into the running repository, not compiled into the image.