Skip to main content
View all authors

GitHub Automation and Authentication

ยท 4 min read

Like it or not, GitHub is where most developers already live - it's where code is written, reviewed, and shipped with or without AI. So rather than asking you to adapt your workflow to Juno, I've been working on bringing Juno closer to yours.

Two new features ship in that direction: a new recommended approach for automating deployments via GitHub Actions, and support for GitHub authentication in your applications ๐Ÿš€.


Automation Without Secretsโ€‹

Until now, setting up GitHub Actions to deploy your frontend or publish serverless functions required storing a JUNO_TOKEN in your GitHub secrets. It worked, but it came with the usual headaches, tokens to generate, rotate, and keep out of the wrong hands.

The new recommended approach uses OpenID Connect (OIDC). Instead of a static token, GitHub and your Satellite establish a trust relationship, and each workflow run gets short-lived credentials (access keys) automatically. Once the run is done, the credentials are gone.

No tokens to rotate. No secrets to manage. And if a workflow is ever compromised, there's nothing long-lived to steal.

To support this, a new Deployments screen has been added to the Console. This is where you configure which repositories are allowed to deploy to your Satellite and where you can review past deployments.

tip

Obviously and as always, you can also configure the same options with your CLI if you prefer.

๐Ÿ‘‰ Documentation

The new Deployments screen in the Juno Console, listing GitHub Actions workflow


GitHub Authenticationโ€‹

You can now add GitHub authentication to your application, letting your users sign in with their GitHub account, a natural fit for developer-facing apps.

Unlike other providers, GitHub does not natively support OpenID Connect. That's why this authentication requires a small proxy that handles the OAuth exchange securely to avoid exposing a client secret directly within your Satellite.

Juno provides an open-source API for this: github.com/junobuild/api. You self-host it, point your configuration to it, and from there the flow is straightforward, the user signs in, the proxy issues a JWT, your Satellite verifies it, and a session is created.

One thing to be aware of though: because the proxy signs JWTs with RSA keys, your Satellite needs access to those public keys to verify them. Rather than having each Satellite make HTTPS outcalls to your proxy on every request, Juno uses a shared infrastructure module called Observatory to fetch and cache those keys. Since you're self-hosting the proxy, you'll also need to deploy your own Observatory instance and configure it to point to your proxy's JWKS endpoint.

It's a bit tricky to set up but worth the effort if you're targeting developers. Reach out if you have questions, always happy to help!

๐Ÿ‘‰ Documentation


One could argue that instead of improving the integration with GitHub, I should spin up custom infrastructure and provide the entire CI/CD for building and deploying. I wouldn't disagree. If the goal were to compete with cloud providers like AWS, Vercel, you name it, that would probably be the path. But devops is not really my thing, and that would be quite an effort both in terms of development and maintenance for the sole developer I am, who already takes care of a gigantic codebase ๐Ÿ˜…. Maybe someday, but honestly it feels unlikely as I doubt Juno will ever become a company.

Until then, I think this is a great step in the right direction - a tighter integration between Juno and the tools you already use day to day. Hope you find it useful too!

To infinity and beyond
David

Architecture Changes for a Better Developer Experience

ยท 4 min read

Happy New Year ๐Ÿฅณ

We're kicking off 2026 with a major release that ships significant changes in the architecture and design of the Juno Console with a single goal: making the DX more straightforward and comprehensive.


Mission Control and Monitoring mergedโ€‹

Mission Control was originally designed as a dedicated control center for developers โ€” a place to create and manage projects (Satellites) and analytics (Orbiters). Over time, it was also extended to include Monitoring.

The original idea was simple: the Console would only know a developer's identity and their Mission Control ID โ€” nothing else.

While this approach had advantages, it also introduced significant drawbacks.

On every new sign-up, the Console had to automatically create a Mission Control. This meant that when a developer signed in and created their first (free) project, Juno had to provision two containers instead of one, increasing infrastructure costs.

It also led to a confusing user experience. Mission Control could not be hidden from the UI because modules must be provisioned with resources (cycles) to avoid being decommissioned. As a result, it was always visible, and many developers were unsure what Mission Control was or why it existed.

For these reasons, Mission Control and Monitoring have now been merged:

  • A Mission Control is created only when a developer explicitly enables Monitoring
  • Monitoring is now treated as a dedicated microservice

This architectural change brings clear benefits:

  • For developers: a clearer, more straightforward experience and simpler long-term maintenance
  • For Juno: acquisition costs for new developers are effectively cut in half

There are a few trade-offs to note:

  • The Juno Console now keeps track of all containers created by developers (Satellites, Orbiters, and Mission Controls), whereas previously it only knew the Mission Control ID.
  • When Monitoring is enabled, module metadata must be duplicated inside the Mission Control so it knows what to monitor. This introduces a risk of inconsistencies if a bug causes a mismatch between the Console and Monitoring data. To mitigate this, a Console feature is planned to compare and verify this information.

Deprecate ICP, use only Cyclesโ€‹

Over the past year, I've been refining both the platform and its communication to reinforce Juno's vision: a platform to build, deploy, and run applications in WASM containers, with ownership and zero DevOps.

As part of this effort, I've aimed to remove all blockchain and crypto-slangs.

While merging Mission Control and Monitoring, a new wallet ID - derived from the developer's sign-in - had to be introduced.

During this work, it became really clear again that the onboarding for new developers was just confusing:

Why do I need ICP to get cycles?

Since this release already introduced breaking changes, it felt like the right moment to simplify the model further.

As a result, ICP is now deprecated in favor of using cycles only.

This significantly simplifies the user journey:

  • You start using Juno for free
  • You learn about cycles as the resource that powers your containers and services
  • When you need more resources or want to spin up additional modules, you acquire cycles

To support this, the primary call to action for acquiring cycles now points to cycle.express, which allows developers to convert dollars directly into cycles. Third-party wallets like OISY remain supported, but are now positioned as secondary options for users already familiar with them.

Together with the other changes in this release, this should make both the developer experience and the mental model of how Juno works much more approachable.


Price increaseโ€‹

Previously, creating new Satellites or Orbiters cost 0.4 ICP. In practice, this was undervalued: each module is provisioned with 1.5 T cycles, while 0.4 ICP corresponds to roughly 0.93 T cyclesโ€”effectively a significant bonus.

Going forward:

  • Additional Satellites and Orbiters cost 3 T cycles (roughly $4).
  • Enabling Monitoring (which spins up a Mission Control) requires the same fee.
note

See the Pricing documentation for more details.


I believe these changes represent a significant step forward in making Juno more accessible and easier to understand. Whether you're just getting started or have been with us for a while, I hope these improvements make your development experience that much better.

To infinity and beyond
David

Juno is now fully open-source

ยท One min read

Hey ๐Ÿ‘‹

Juno is now fully open-source. No more AGPL. The project is now entirely released under the MIT license.

While all the tools were already licensed under MIT, the platform itself, a few crates, and the documentation were still released under AGPL. That worked well in the early days but can create friction for those who want to reuse Juno, or parts of it, in commercial projects or corporate environments. Plus, AGPL is not that cool, no? ๐Ÿ˜Ž

You can also consider this a small Christmas gift ๐ŸŽ๐Ÿ˜‰

Thank you to everyone who followed, tested, used, and supported Juno this year. I wish you a Merry Christmas and a fantastic New Year 2026.

You can find the source code on GitHub: ๐Ÿ‘‰ https://github.com/junobuild/juno

Merry Christmas from Zรผrich ๐ŸŽ„
David

Serverless Canister Calls in TypeScript

ยท One min read

Hey ๐Ÿ‘‹

If you like working with ic-js in the frontend...
Say hello to serverless canister functions in TypeScript โšก๏ธ

Write backend logic using the same TypeScript you already love โ€” now with:

  • ๐Ÿ’ซ Built-in canister clients for the Internet Computer (ICP, ICRC, CMC, NNS, SNS...)
  • โš™๏ธ Full type-safety
  • ๐Ÿ”Œ Zero agent setup
  • ๐Ÿง  Caller identity handled automatically
  • ๐Ÿฑ ICP & ICRC transfers from serverless hooks

No Rust required. No backend headaches.


๐Ÿ“š Documentationโ€‹

Want to go straight to the point? Checkout the ๐Ÿ‘‰ references


Exampleโ€‹

Transfer ICP directly from a Satellite serverless function:

import { IcpLedgerCanister } from "@junobuild/functions/canisters/ledger/icp";

export const onExecute = async () => {
const ledger = new IcpLedgerCanister();

const result = await ledger.transfer({
args: {
to: "destination-account-identifier",
amount: { e8s: 100_000_000n }, // 1 ICP
fee: { e8s: 10_000n },
memo: 0n
}
});
};

And yes - this works inside datastore hooks like onSetDoc and assertSetDoc, fully atomic.

Browse the full working example ๐Ÿ‘‰ Making Canister Calls in TypeScript

Cool cool cool?

To infinity and beyond
David

Custom Domains Support Upgrade

ยท 2 min read

Hey ๐Ÿ‘‹

A new release was shipped with two sweet improvements to the Console.

๐ŸŒ Custom Domains New APIโ€‹

The hosting features for registering or administrating custom domains have been migrated to the API from the DFINITY Boundary Nodes team.

This migration makes managing domains:

  • More reliable
  • Significantly faster
  • Future-proof

Massive kudos to the team delivering this capability - awesome work ๐Ÿ’ช

โœจ UI Improvementsโ€‹

Alongside the domain upgrade, we shipped a few visual refinements:

Stronger contrast on cards and buttons for a cleaner, more readable interface (amazing what you can achieve by nudging brightness ๐Ÿ˜„)

The Launchpad has been slightly redesigned: "Create a new satellite" is now a primary button, bringing more clarity and guidance.

A screenshot of the launchpad that showcases the new contrast and actions that have been moved

Another screenshots from the authentication setup which displays the new tabs design

To infinity and beyond
David

Google Sign-In Comes to Juno

ยท 6 min read


TL;DR

You can now use your Google account to log into the Juno Console, and developers can add the same familiar login experience natively to the projects they are building.

Hey everyone ๐Ÿ‘‹

Today marks quite a milestone and I'm excited to share that Google Sign-In is now live across the entire Juno ecosystem.

From my perspective, though time will tell, this update has the potential to be a real game changer. It brings what users expect: a familiar, secure, and frictionless authentication flow.

It might sound a bit ironic at first - we're integrating Google, after all - but I'm genuinely happy to ship this feature without compromising on the core values: providing developers secure features and modern tools with a state-of-the-art developer experience, while empowering them with full control over a cloud-native serverless infrastructure.

Let's see how it all comes together.


๐Ÿ’ก Why It Mattersโ€‹

Authentication is one of those things every product needs but, it's complex, it touches security, and it's easy to get wrong.

Until now on Juno, developers could use Internet Identity, which has its strengths but also its weaknesses. It provides an unfamiliar login flow - is it an authentication provider or a password manager? - and it's not a well-known product outside of its niche.

Passkeys were also added recently, but you only have to scroll through tech Twitter to see that for every person who loves them, there's another who absolutely hates them.

That's why bringing native Google Sign-In to Juno matters. Developers can now offer their users a familiar, frictionless login flow - and let's be honest, most people are used to this signing and don't care much about doing it differently.

At the same time, this doesn't mean giving up control. The authentication process happens entirely within your Satellite, using the OpenID Connect standard.

You can obviously combine multiple sign-in methods in one project, offering your users the choice that best fits their needs.

When it comes to Juno itself, this also matters for two reasons: it potentially makes onboarding - through the Console - more accessible for web developers who don't care about decentralization but do care about owning their infrastructure ("self-hosting"). And it opens the door to future integrations with other providers. I still hope one day to have a better GitHub integration, and this might be a step toward it.

Long story short, it might look like a trivial change - just a couple of functions and a bit of configuration - but it's another step toward Juno's long-term goal of making it possible to build and scale modern cloud products without compromising on what matters most: empowering developers and their users.


โš™๏ธ How It Worksโ€‹

When a user signs in with Google, Juno follows the OpenID Connect (OIDC) standard to keep everything secure and verifiable.

  1. The user signs in with Google.
  2. Google verifies their credentials and issues a signed OpenID Connect token.
  3. After redirecting to your app, that signed token (JWT) is sent to your Satellite.
  4. Inside the container, the token and its signature are verified, and the user's information (such as email or profile) is extracted.
  5. The Satellite then creates a secure session for the user.
  6. Once authenticated, the user can start interacting with your app built on top of your container's features.

๐Ÿงฉ Infrastructureโ€‹

At this point, you get the idea: aside from using Google as a third-party provider, there's no hidden โ€œbig techโ€ backend behind this. Everything else happens within your Satellite.

The credentials you configure - your Google project and OAuth 2.0 Client ID - are yours. In comparison, those used in Internet Identity are owned by the DFINITY Foundation. So, this approach might feel less empowering for end users or more empowering for developers. You can see the glass half full or half empty here.

To validate tokens on the backend, your container needs access to the public keys Google uses to sign them. Since those keys rotate frequently, fetching them directly would introduce extra cost and resource overhead.

That's why the Observatory - a shared module owned by Juno (well, by me) - comes in. It caches and provides Google's public keys, keeping verification fast, efficient, and cost-effective.

Because Juno is modular, developers who want full control or higher redundancy can run their own Observatory instance. Reach out if you're interested.


๐Ÿช„ Setup Overviewโ€‹

Getting started only takes a short configuration. Once your Google project is set up, add your Client ID to your juno.config file:

import { defineConfig } from "@junobuild/config";

export default defineConfig({
satellite: {
ids: {
development: "<DEV_SATELLITE_ID>",
production: "<PROD_SATELLITE_ID>"
},
source: "dist",
authentication: {
google: {
clientId: "1234567890-abcde12345fghijklmno.apps.googleusercontent.com"
}
}
}
});

Then apply it using the CLI or manually through the Console UI. That's it, it's configured.


๐Ÿง‘โ€๐Ÿ’ป Usageโ€‹

To add the sign-in to your app, it only takes a single function call - typically tied to a button like "Continue with Google".

import { signIn } from "@junobuild/core";

await signIn({ google: {} });

For now, it uses the standard redirect flow, meaning users are sent to Google and then redirected back to your app.

You'll just need to handle that callback on the redirect route with:

import { handleRedirectCallback } from "@junobuild/core";

await handleRedirectCallback();

I'll soon unleash support for FedCM (Federated Credential Management) as well.

Aside from that, nothing new - the rest works exactly the same.

Regardless of which authentication provider you're using, you can still track a user's authentication state through a simple callback:

import { onAuthStateChange } from "@junobuild/core";

onAuthStateChange((user: User | null) => {
console.log("User:", user);
});

And because type safety is the way, you can now safely access provider-specific data without writing endless if statements:

import { isWebAuthnUser, isGoogleUser } from "@junobuild/core";

if (isWebAuthnUser(user)) {
console.log(user.data.providerData.aaguid); // Safely typed โœ…
}

if (isGoogleUser(user)) {
console.log(user.data.providerData.email); // Safely typed โœ…
}

๐Ÿ› ๏ธ Managing Usersโ€‹

Once users start signing in, you can view and manage them directly in the Authentication section of the Console.

Each user entry displays key details such as:

  • Name and email address
  • Authentication provider
  • Profile picture (if available)

This view also lets you filter, sort, refresh or ban users etc.

Screenshot of the Juno Console showing users authenticated with Google


๐Ÿ“š Learn Moreโ€‹

You can find all the details - including setup, configuration, and advanced options - in the documentation:

If you haven't tried Juno yet, head over to console.juno.build and sign in with Google to get started.

Ultimately, I can tell you stories, but nothing beats trying it yourself.

To infinity and beyond,
David


Reach out on Discord or OpenChat for any questions.

Stay connected with Juno on X/Twitter.

โญ๏ธโญ๏ธโญ๏ธ stars are also much appreciated: visit the GitHub repo and show your support!

New (kind of) breadcrumbs nav, who dis?

ยท 2 min read

A screenshot of the new Console UI with navigation on the Datastore page

I tagged a new release as I deployed a new version of the Console UI to mainnet.

Aside from the updated navigation, which now displays the page title within breadcrumb-style navigation, and a few minor fixes, not much has changed feature-wise.

The biggest change in the frontend's codebase, which explains why so many files were touched, is a refactor to adopt the new pattern Iโ€™ve been using for DID declarations.

Instead of relying on auto-imported separate types, I now prefer grouping factories in a single module, exporting them from there, and importing the types through a suffixed module DID alias.

You can check out the pattern in Juno's frontend codebase or in the ic-client JS library. If you're curious about it, let me know.

Itโ€™s a small structural shift that makes the code much cleaner and more readable.

Finally, there are a few new E2E tests added in this repo and in the CLI.

To infinity and beyond ๐Ÿžโœจ
David

Offline Snapshots with the CLI

ยท One min read

Hi ๐Ÿ‘‹

Here is a small but handy update for your toolbox: you can now download and upload snapshots offline with the Juno CLI. ๐Ÿงฐ

That means you can:

  • Keep a local copy of your moduleโ€™s state
  • Stash it somewhere safe, just in case ๐Ÿ˜…
  • Restore it when needed
  • Or even move it between Satellites
juno snapshot download --target satellite
juno snapshot upload --target satellite --dir .snapshots/0x00000060101

Build & Run Scripts with โ€œjuno runโ€

ยท 2 min read

Say hello to juno run ๐Ÿ‘‹

Build custom scripts that already know your env, profile & config.
Write them in JS/TS.
Run with the CLI. โšก๏ธ

๐Ÿ’ on top? Works out of the box in GitHub Actions!

For example:

import { defineRun } from "@junobuild/config";

export const onRun = defineRun(({ mode, profile }) => ({
run: async ({ satelliteId, identity }) => {
console.log("Running task with:", {
mode,
profile,
satelliteId: satelliteId.toText(),
whoami: identity.getPrincipal().toText()
});
}
}));

Run it with:

juno run --src ./my-task.ts

Now, letโ€™s suppose you want to fetch a document from your Satelliteโ€™s Datastore (โ€œfrom your canisterโ€™s little DBโ€) and export it to a file:

import { getDoc } from "@junobuild/core";
import { defineRun } from "@junobuild/config";
import { jsonReplacer } from "@dfinity/utils";
import { writeFile } from "node:fs/promises";

export const onRun = defineRun(({ mode }) => ({
run: async (context) => {
const key = mode === "staging" ? "123" : "456";

const doc = await getDoc({
collection: "demo",
key,
satellite: context
});

await writeFile("./mydoc.json", JSON.stringify(doc, jsonReplacer, 2));
}
}));

Fancy โœจ

And since itโ€™s TS/JS, you can obviously use any libraries to perform admin tasks as well.

import { defineRun } from "@junobuild/config";
import { IcrcLedgerCanister } from "@dfinity/ledger-icrc";
import { createAgent } from "@dfinity/utils";

export const onRun = defineRun(({ mode }) => ({
run: async ({ identity, container: host }) => {
if (mode !== "development") {
throw new Error("Only for fun!");
}

const agent = await createAgent({
identity,
host
});

const { metadata } = IcrcLedgerCanister.create({
agent,
canisterId: MY_LEDGER_CANISTER_ID
});

const data = await metadata({});

console.log(data);
}
}));

Coolio?

Iโ€™ll demo it next Monday in Juno Live. ๐ŸŽฅ https://youtube.com/@junobuild

Happy week-end โ˜€๏ธ