Skip to content

Upgrading Opsting

This guide covers upgrading an existing Opsting installation to a newer release.

Coming from Atelier (v0.26.0-beta or earlier)? This guide does not apply. Opsting v0.27.0-beta renamed the Kubernetes namespaces, the resource labels and the image names, so there is no upgrade path from an Atelier install — and upgrade.sh will not find your deployments. Follow Migrating from Atelier to Opsting instead: you stand up a fresh install alongside the old one and move your apps across, with your source and app data intact.

Release notes — what’s in this version

Before upgrading, read the release notes for the version you’re moving to. They live alongside the binaries on Azure Blob Storage and are reachable without any GitHub login:

https://tryatelier.blob.core.windows.net/tryatelier/latest/release-notes.md # most recent release
https://tryatelier.blob.core.windows.net/tryatelier/v0.9.7-beta/release-notes.md # pinned to a specific tag

Most browsers display markdown as plain text; curl <url> | less works in a terminal. Every tag from v0.9.7-beta onwards has a corresponding release-notes.md — release workflows that don’t include one fail before any binaries ship.

Upgrade paths

Two paths are supported, each suited to a different network situation:

  • GHCR modekubectl set image against ghcr.io/opsting-project/opsting-core:vX.Y.Z and opsting-ui:vX.Y.Z. Requires the cluster to reach ghcr.io. Recommended for all v0.9.8-beta+ clusters.
  • Tarball mode — import opsting-core-image.tar.gz / opsting-ui-image.tar.gz / mcp-fetch-image.tar.gz into k3s containerd, then rollout-restart. Only works for upgrading clusters originally installed before v0.9.8-beta (post-Phase-2A manifests reference versioned ghcr.io/... tags that a tarball import doesn’t satisfy). Versioned offline upgrade is tracked in the offline-install follow-up epic.

The same upgrade.sh script handles both — it picks the mode automatically based on whether the first argument looks like a version tag (vX.Y.Z or vX.Y.Z-suffix).


Prerequisites

  • An existing Opsting install (v0.27.0-beta or later) running on a node you can SSH to. Atelier installs take the migration path above instead.
  • Shell access to that node with permission to run sudo k3s ... and kubectl against the local cluster (the KUBECONFIG defaults to /etc/rancher/k3s/k3s.yaml).
  • For GHCR mode: outbound HTTPS to ghcr.io from the cluster node. No GitHub account, no PAT, no pull secret needed — the platform packages are public.
  • For tarball mode: enough disk space for the downloaded tarballs (~200 MB total) and a way to get them onto the node (download directly, scp, USB stick, etc).

About the download URLs. Release artifacts and release notes are mirrored to https://tryatelier.blob.core.windows.net/tryatelier/ so testers can curl them anonymously. latest/ always points at the most recent release; pin a specific version by swapping latest for the tag (e.g. v0.9.7-beta).

Why does that URL say “tryatelier”? Opsting was called Atelier until v0.27.0-beta. The storage container kept its original name deliberately: it is only a download path, and renaming it would break every link already published in older release notes and install guides without improving anything. The files it serves are the current Opsting ones. You will see the old name in a few other places for the same reason — the support address tryatelierai@gmail.com, and tryatelier.dev, which still serves these docs with a notice pointing here.


Terminal window
# Run on the cluster node, in any directory.
curl -Lo upgrade.sh https://tryatelier.blob.core.windows.net/tryatelier/latest/upgrade.sh
chmod +x upgrade.sh
./upgrade.sh v0.9.5-beta

What this does, in order:

  1. Detects v0.9.5-beta looks like a version tag → enters GHCR mode.
  2. Runs kubectl set image deployment/opsting-core opsting-core=ghcr.io/opsting-project/opsting-core:v0.9.5-beta -n opsting.
  3. Repeats for opsting-ui.
  4. Waits for both Deployments to roll out (180-second timeout per Deployment).
  5. Reports completion.

The user-facing UI may briefly show a “reconnecting” indicator while the new pods come up — typically a few seconds. App data is unaffected; running app pods are not restarted.

Targeting individual components

Terminal window
./upgrade.sh v0.9.5-beta core # core only
./upgrade.sh v0.9.5-beta ui # ui only

What about mcp-fetch?

From v0.9.8-beta onwards, opsting-core’s catalog bootstrap deploys mcp-fetch from ghcr.io/opsting-project/opsting-mcp-fetch:<core-version> directly — the version pin tracks the running core, so a v0.9.8-beta core deploys v0.9.8-beta mcp-fetch. Fresh installs and re-creations of the deployment use this path automatically.

upgrade.sh still does not rewrite an existing mcp-fetch deployment. Clusters that came up before v0.9.8-beta retain their old registry.opsting.local/mcp-fetch:latest deployment after upgrading core/ui — that keeps working but doesn’t move to GHCR. To migrate an existing cluster’s mcp-fetch, delete and re-deploy the MCP server via Settings → MCP Servers; the catalog will recreate it with the GHCR ref.


Tarball mode — offline / air-gapped

Compatibility note. Tarball-mode upgrade only works on clusters originally installed before v0.9.8-beta. v0.9.8-beta and later install platform deployments with image: ghcr.io/opsting-project/...:vX.Y.Z — a tarball import lands as registry.opsting.local/...:latest, so a rollout-restart won’t pick it up. Versioned offline upgrade and offline fresh-install are tracked in the offline-install follow-up epic.

Terminal window
# Run on the cluster node, in any directory.
curl -Lo upgrade.sh https://tryatelier.blob.core.windows.net/tryatelier/latest/upgrade.sh
curl -Lo opsting-core-image.tar.gz https://tryatelier.blob.core.windows.net/tryatelier/latest/opsting-core-image.tar.gz
curl -Lo opsting-ui-image.tar.gz https://tryatelier.blob.core.windows.net/tryatelier/latest/opsting-ui-image.tar.gz
curl -Lo mcp-fetch-image.tar.gz https://tryatelier.blob.core.windows.net/tryatelier/latest/mcp-fetch-image.tar.gz
chmod +x upgrade.sh
./upgrade.sh

(For genuinely air-gapped clusters, swap the curl commands for whatever transport you use to get files in — scp, USB, internal artifact store, etc. The script only cares that the four files end up in the same directory.)

What this does, in order:

  1. No version argument → enters tarball mode.
  2. For each tarball found alongside the script: gunzipsudo k3s ctr -n k8s.io images import to load it into containerd.
  3. For mcp-fetch: also pushes the image to the in-cluster registry (localhost:32000) so opsting-core can pull it on demand for the MCP catalog.
  4. kubectl rollout restart on the affected Deployments.
  5. Waits for rollouts.

Manifests use imagePullPolicy: IfNotPresent, so the rollout-restart picks up the freshly-imported image without needing to change the image tag.


Rolling back

For both modes, the cleanest rollback is kubectl rollout undo:

Terminal window
kubectl rollout undo deployment/opsting-core -n opsting
kubectl rollout undo deployment/opsting-ui -n opsting
kubectl rollout status deployment/opsting-core -n opsting
kubectl rollout status deployment/opsting-ui -n opsting

This reverts each Deployment to the previous ReplicaSet — same effect as re-running the upgrade with the previous version tag.

To roll back to an arbitrary earlier version:

Terminal window
./upgrade.sh v0.9.4-beta # GHCR mode
# or
./upgrade.sh # tarball mode (with the older tarballs alongside)

Verifying the upgrade

Terminal window
# Pods running the new images:
kubectl get pods -n opsting -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
# Recent opsting-core logs:
kubectl logs -n opsting deployment/opsting-core --tail=50
# Confirm the portal is reachable:
curl -sS -o /dev/null -w "%{http_code}\n" http://opsting.local/

Troubleshooting

ErrImagePull on the new pod (GHCR mode)

Means containerd can’t reach ghcr.io or the requested tag doesn’t exist.

  1. Check tag exists:

    Terminal window
    curl -sS https://ghcr.io/v2/opsting-project/opsting-core/tags/list | head

    If you don’t see your version, the release workflow probably hasn’t finished publishing yet — wait a few minutes after a tag push.

  2. Check connectivity from the node:

    Terminal window
    sudo k3s crictl pull ghcr.io/opsting-project/opsting-core:v0.9.5-beta
  3. Confirm packages are public: the check that matters is an anonymous registry pull, not the GitHub UI — the repo-scoped /pkgs/container/ URL 404s even for public packages unless they happen to be linked to that repo.

    Terminal window
    docker pull ghcr.io/opsting-project/opsting-core:<version>

    If that needs credentials the package is private — flip its visibility under the org’s packages.

kubectl set image returns “no such deployment”

Your cluster’s namespace or Deployment names differ from the defaults. Check with kubectl get deploy -A | grep opsting. The script assumes namespace opsting and Deployment names opsting-core / opsting-ui. Both namespaces are overridable — set OPSTING_PLATFORM_NAMESPACE for the control plane and OPSTING_APPS_NAMESPACE for apps; edit the script directly if the Deployment names differ too.

Tarball mode reports “no images found”

upgrade.sh resolves tarballs relative to its own directory, not your CWD. Make sure all four *-image.tar.gz files are alongside the script itself.

Rollout times out

Default per-Deployment timeout is 180 seconds. If your image pull is slow (large image, slow link), watch live with kubectl rollout status deployment/opsting-core -n opsting --watch and re-run the upgrade after the pull completes — rollouts are idempotent at the same image tag.

”Unknown target ‘foo’”

You passed a component selector that isn’t core, ui, or mcp-fetch. The version tag (if any) must come first, before any selectors:

Terminal window
./upgrade.sh v0.9.5-beta core # ✓
./upgrade.sh core v0.9.5-beta # ✗ — interpreted as tarball-mode, target 'core', then unknown target 'v0.9.5-beta'

What gets upgraded vs preserved

ResourceBehaviour during upgrade
opsting-core podReplaced with the new image (RollingUpdate).
opsting-ui podReplaced with the new image.
SQLite DB (/data/opsting.db)Preserved on its PVC.
App workloads (opsting-apps namespace)Untouched. Running apps stay up across the upgrade.
Secrets (opsting-secrets)Untouched.
Gitea + registry podsUntouched.

If a release ever requires a destructive migration (DB schema change, breaking config rename, etc.), the release notes will call it out explicitly. The default assumption is upgrades are safe to run during the working day.