I wanted a real Linux desktop app for Codex.
At first, this looked like it should be straightforward: take the official Windows package, unpack it, swap in Linux Electron, rebuild whatever was platform-specific, and turn it into a .deb.
That is not what happened.
What actually worked was more pragmatic: preserve the official Codex payload, branding, and resources from the Windows MSIX, but replace the runtime shell with a small Linux Electron wrapper that opens Codex in its own desktop window.
This post is the write-up I wish I had before I started.
The Goal
The target was simple:
- install Codex on Ubuntu as a
.deb - get a desktop launcher and icon
- make it feel like a native app
- avoid relying on a browser tab
- keep the build repeatable
I already had something similar working for ChatGPT, so I assumed Codex would follow the same path.
That assumption was wrong.
What I started with
The source payload was the official Windows MSIX:
OpenAI.Codex_26.313.5234.0_x64__2p2nqsd0c76g0.Msix
The repo I ended up with is here:
git@github.com:johnohhh1/codex-ubuntu.git
The main build script is:
./build-codex-native-deb.sh
The obvious approach
The first plan was the normal "repackage the Electron app" playbook:
- Extract the MSIX.
- Grab the bundled
app.asar. - Swap the Windows Electron runtime for Linux Electron.
- Rebuild or replace native modules.
- Repackage everything into a
.deb.
In theory, that should have produced a Linux .deb built directly from the original app bundle.
In practice, it failed in multiple different ways.
Problem 1: the Windows bundle was not actually Linux-ready
The Codex payload included resources and binaries that looked promising, but the packaged app still carried Windows assumptions.
The biggest issue was native modules.
At one point I confirmed the app was dying because a native dependency was still Windows-built:
invalid ELF header
That is the classic sign that Linux is trying to load a binary module compiled for the wrong platform.
So the next move was to rebuild native modules for Linux and wire them back into the extracted app.
That still did not get me to a usable app window.
Problem 2: getting the app to "launch" was not the same as getting it to work
I got as far as installing the package cleanly and starting Electron on Linux.
That was progress, but not enough.
The app process would launch and then exit without showing a window, or fail during startup after getting past the first set of issues.
This is one of the most annoying parts of Electron packaging work: "the binary started" sounds good, but it does not mean the app is actually viable.
Problem 3: Electron GPU startup was fatal on Linux
Once I simplified the runtime enough to observe the real failure clearly, the next blocker showed up:
FATAL: GPU process isn't usable. Goodbye.
That changed the direction of the fix immediately.
Instead of trying to preserve every part of the original desktop behavior, I needed a Linux-friendly launcher path that was stable in the actual Ubuntu session I was using.
The fix was to disable GPU at both levels:
- in the Electron app itself with command-line switches
- in the launcher wrapper used by the packaged
.deb
The final launcher executes Electron like this:
exec /opt/codex-desktop-native/electron/Codex \
--no-sandbox \
--disable-setuid-sandbox \
--disable-gpu \
--disable-gpu-compositing "$@"
That was not elegant, but it was effective.
The pivot: stop trying to preserve the original runtime shell
This was the real turning point.
Instead of continuing to patch the original Windows desktop runtime deeper and deeper, I switched to a simpler architecture:
- use Linux Electron as the runtime
- keep the official Codex assets from the MSIX
- build a tiny Linux wrapper app
- load
https://chatgpt.com/codexinside a dedicated desktop window
That approach still let me build from the official Windows MSIX, but it stopped me from getting trapped in the least portable parts of the original runtime.
What the final wrapper does
The generated wrapper app is very small.
It:
- creates a
BrowserWindow - hides the menu bar
- uses a Codex title and icon
- opens external popups in the default browser
- allows normal
httpandhttpsnavigation - forces the window to show even if page paint is slow
- points at
https://chatgpt.com/codexby default
The wrapper also supports overriding the URL with an environment variable:
CODEX_WEB_URL=https://chatgpt.com/codex codex-desktop-native
That is useful if the endpoint ever changes or if you want a staging/test flow.
Packaging details that mattered
There were a few packaging details that turned out to be more important than I expected.
1. The app had to present itself as Codex
I renamed the Linux Electron binary from electron to Codex so the app identity looked correct on Linux.
That helped with launcher behavior and window class handling.
2. Desktop entry details mattered
The .desktop file sets:
Name=CodexExec=codex-desktop-nativeIcon=codex-desktop-nativeStartupWMClass=CodexX-GNOME-WMClass=Codex
Without that, you can end up with weird launcher grouping or desktop integration issues.
3. Old install leftovers had to be cleaned up explicitly
Earlier experiments left behind app.asar.unpacked content from previous versions.
Even after the packaging approach changed, those leftovers could survive upgrades and create confusing behavior.
So I added a preinst cleanup step to the package:
rm -rf "/opt/codex-desktop-native/electron/resources/app.asar.unpacked"
That made upgrades deterministic again.
Making the build self-contained
At first I was borrowing tools from another workspace, which worked but was brittle.
I cleaned that up by making the project self-contained with local dev dependencies:
{
"devDependencies": {
"asar": "^3.2.0",
"electron": "^40.0.0"
}
}
That way the repo can be built with:
npm install
./rebuild-install.sh
instead of depending on some other directory on disk having the right binaries in it.
The build flow now
The working build pipeline looks like this:
- Validate the MSIX input.
- Extract the package and detect its version from
AppxManifest.xml. - Copy the official assets into a staging directory.
- Copy in Linux Electron.
- Replace the original runtime shell with a generated Linux wrapper
app.asar. - Build a
.debwith the desktop entry, icon, launcher script, and package hooks.
The helper script for rebuild and reinstall is:
./rebuild-install.sh
The result
The final output is a package like this:
dist/codex-desktop-native_26.313.5234.0_amd64.deb
And the user-facing launch command is simply:
codex-desktop-native
Most importantly: it actually opens, stays up, and works as a desktop app on Ubuntu.
What I learned
The biggest lesson from this project is that "porting" is sometimes the wrong framing.
I went in thinking I needed to preserve the original Windows desktop runtime as faithfully as possible.
What I really needed was:
- a reliable Linux package
- a clean desktop launcher
- Codex in its own app window
- a build process I could rerun later
Once I optimized for that instead of purity, the solution got much simpler.
That tradeoff is worth stating directly: the final app is absolutely built from the official Windows MSIX, but it is not a perfect binary carryover of the original runtime. It is a Linux-native wrapper around the official Codex experience, using the original payload where it helps and replacing the parts that did not survive the platform jump well.
That was the right call.
If you want to try it
The repo is here:
git@github.com:johnohhh1/codex-ubuntu.git
Quick start:
git clone git@github.com:johnohhh1/codex-ubuntu.git
cd codex-ubuntu
npm install
./rebuild-install.sh
codex-desktop-native
Closing thought
A lot of cross-platform packaging work looks like a debugging problem, but the real work is architectural judgment.
You have to decide when to keep forcing the original design, and when to admit that a thinner, more native solution is the thing that will actually ship.
This project only started working once I made that call.
- a build process I could rerun later
Once I optimized for that instead of purity, the solution got much simpler.
That tradeoff is worth stating directly: the final app is absolutely built from the official Windows MSIX, but it is not a perfect binary carryover of the original runtime. It is a Linux-native wrapper around the official Codex experience, using the original payload where it helps and replacing the parts that did not survive the platform jump well.
That was the right call.
If you want to try it
The repo is here:
git@github.com:johnohhh1/codex-ubuntu.git
Quick start:
git clone git@github.com:johnohhh1/codex-ubuntu.git
cd codex-ubuntu
npm install
./rebuild-install.sh
codex-desktop-native
Closing thought
A lot of cross-platform packaging work looks like a debugging problem, but the real work is architectural judgment.
You have to decide when to keep forcing the original design, and when to admit that a thinner, more native solution is the thing that will actually ship.
This project only started working once I made that call.
Top comments (0)