Dev

Set Up Python, Node.js, Go and Rust on Fedora 45 / 44

A handful of dnf commands and one rustup-init give you a working Fedora dev environment for Python, Node.js, Go and Rust. Every installer comes from Fedora’s own repositories, but two defaults trip people up on the way. The “Development Tools” group never lists a compiler and gives you no C++ one. Fedora’s packaged uv also won’t download a Python by itself, so uv venv --python 3.14 fails on a fresh Fedora 45 machine until dnf adds that interpreter.

Original content from computingforgeeks.com - post 167933

This guide sets up each language the way that’s least likely to break later: Python with venv and uv, Node.js from the versioned packages, Go from dnf and Rust through Fedora’s packaged rustup. Each section ends with a check, and the last one builds a hello program in every language. Built and run on a Fedora 45 cloud image in October 2026 with Python 3.15, Node.js 24, Go 1.27 and Rust 1.99, with the installs, rustup and hello programs repeated on Fedora 44.

Check what Fedora already ships

Start with a quick inventory, because the answer changes which steps you can skip. This asks for the interpreter, pip and every package the rest of the guide installs.

python3 --version; python3 -m pip --version
rpm -q gcc make git-core nodejs golang rust cargo rustup uv python3-devel python3-setuptools python3-pip

On the Fedora 45 cloud image, Python and Git were the only things already there. pip was not installed as a system module, and there was no compiler, no make and none of the other three runtimes:

Python 3.15.0
/usr/bin/python3: No module named pip
package gcc is not installed
package make is not installed
git-core-2.55.0-2.fc45.x86_64
package nodejs is not installed
package golang is not installed
package rust is not installed
package cargo is not installed
package rustup is not installed
package uv is not installed
package python3-devel is not installed
package python3-setuptools is not installed
package python3-pip is not installed

Missing pip is not a problem, and you don’t need the python3-pip package for project work. Every environment you create with python3 -m venv gets its own pip, and uv environments don’t need one because uv installs packages itself. A system pip mostly tempts you into sudo pip install. The missing compiler matters more, because all four languages lean on it, and it’s the first thing the next section fixes. Workstation and other editions start from a bigger package set, so run the check on your own machine and skip any step whose package already reports as installed.

Install a C compiler first

Every one of the four languages ends up calling a C compiler sooner or later, so install it before the runtimes. These are the places you’ll hit it.

  • Python packages that have no prebuilt wheel compile their C extensions on install.
  • npm packages with native addons build them with node-gyp.
  • Go uses it for any package that imports C through cgo.
  • Rust needs cc as its linker, even for a plain hello world.

Older guides tell you to install the “Development Tools” group for this, but that group doesn’t list GCC. On Fedora 45 its only mandatory package is gettext, and its defaults include Git, patch, Subversion, Doxygen and SystemTap. The compiler packages belong to a separate group, as dnf group info shows.

dnf group info development-tools c-development

GCC, gcc-c++ and make appear only under c-development, together with gdb, strace and the autotools. The development-tools half of the output lists no compiler at all. You still end up with one if you install it, though only by accident. SystemTap’s development package requires gcc and make, so on a gcc-free Fedora 44 machine the group brought in gcc, plus kernel-devel (238 MiB installed) through a weak dependency, and still no gcc-c++. Anything that compiles C++ fails on a machine set up that way.

Id                   : c-development
Name                 : C Development Tools and Libraries
Description          : These tools include core development tools such as automake, gcc and debuggers.
Installed            : no
Order                :
Langonly             :
Uservisible          : yes
Repositories         : fedora
Mandatory packages   : autoconf
                     : automake
                     : binutils
                     : bison
                     : flex
                     : gcc
                     : gcc-c++
                     : gdb
                     : glibc-devel
                     : libtool
                     : make
                     : pkgconf
                     : strace

Both groups together pulled 228 packages and a 362 MiB download on Fedora 45, including debuggers, profilers, documentation tools and version control systems you may never open. Naming the three packages you actually need is far lighter, and it keeps dnf upgrade from dragging a few hundred extra packages along for years. On Fedora 44 this was 11 packages and a 90 MiB download, with weak dependencies switched off so dnf installs nothing you didn’t name.

sudo dnf install -y --setopt=install_weak_deps=False gcc gcc-c++ make

Install c-development instead if you also want gdb, valgrind and perf on the machine. Either way, check that cc resolves, since that is the name Rust and most build scripts call:

gcc --version | head -1; make --version | head -1; cc --version | head -1

All three should report a version, and cc should report the same GCC build as gcc. If cc says command not found, the compiler is not installed, whatever groups you added, and Rust will fail at its link step later.

gcc (GCC) 16.2.1 20260819 (Red Hat 16.2.1-2)
GNU Make 4.4.1
cc (GCC) 16.2.1 20260819 (Red Hat 16.2.1-2)

Set up Python with venv and uv

Fedora’s python3 belongs to the system, and other Fedora packages expect its modules to be the versions dnf installed, so don’t install project packages into it. Give each project a virtual environment instead. The built-in venv module works straight away and bundles pip.

python3 -m venv ~/dev-test/.venv && ~/dev-test/.venv/bin/pip --version

That pip comes from Fedora’s python-pip-wheel package, which venv copies into every new environment, and the path confirms it landed inside the environment rather than in the system site-packages.

pip 26.1.2 from /home/joshua/dev-test/.venv/lib/python3.15/site-packages/pip (python 3.15)

For anything beyond a throwaway script, uv is the faster tool. It creates environments, resolves and installs packages, and manages a project’s lock file. Fedora packages it, so install it with the Python headers, which compile C extensions when a package has no prebuilt wheel.

sudo dnf install -y uv python3-devel
uv --version

Fedora’s stable uv trailed PyPI by seven releases on the day of testing, including a minor version bump, and a newer build was already waiting in updates-testing. Expect the version here to be older than the one on the uv website.

uv 0.12.19 (x86_64-unknown-linux-gnu)

That is fine for daily use. Don’t try uv self update to catch up, though. The packaged build refuses with “uv was installed through an external package manager and cannot update itself”, so upgrade uv with dnf like any other package.

Get a second Python version for uv

Fedora’s default Python is newer than some frameworks support, so a project may ask for an older interpreter. Upstream uv would download one for you. Fedora’s uv won’t, because the package ships /etc/uv/uv.toml with two settings that change its defaults:

python-downloads = "manual"
python-preference = "system"

With downloads set to manual, asking for a version that isn’t installed fails instead of fetching it. Fedora 45 packages the two previous Python series as python3.14 and python3.13, so the fix is one dnf command, after which uv finds /usr/bin/python3.14 on its own.

uv venv --python 3.14 fails on Fedora 45 with Python downloads set to manual, then works after dnf install python3.14

Prefer the dnf interpreter when Fedora packages the version you need, because it gets Fedora’s security updates with the rest of the system. When you’d rather have uv manage an interpreter, or Fedora doesn’t carry the version at all, ask for it by name with uv python install. That still works, because the manual setting only blocks downloads uv would start on its own. It put a 108 MB uv-managed build of python3.13 under ~/.local/share/uv/python in five seconds.

uv python install 3.13

uv also adds a python3.13 link in ~/.local/bin, which is already on Fedora’s default PATH. With an interpreter in place, a uv project takes three commands to create, add a dependency to and run.

mkdir -p ~/app3 && cd ~/app3
uv init --python 3.14
uv add requests
uv run python -c "import sys,requests;print(sys.version.split()[0], requests.__version__)"

The last line runs inside the project’s environment without activating it. It prints the interpreter and library versions, and the first number should match the version you asked for.

3.14.7 2.34.2

Keep the plain uv init here, because it writes a .python-version file that pins the project to the version you asked for. With uv init --bare --python 3.14, which skips that file, the same steps in a fresh directory built the environment on the default interpreter and printed 3.15.0. The bare project only records requires-python = ">=3.14", and the default /usr/bin/python3 satisfies that, so uv used it. Check uv run python --version once in any project you created with --bare.

Don’t worry about the new default Python breaking the usual libraries. The numpy, pydantic, pandas, lxml, cryptography and psycopg2-binary all installed from prebuilt wheels into an environment on the default interpreter, with nothing compiled, and numpy plus pydantic took five seconds. Only a package without a wheel falls back to the compiler and python3-devel.

Fix “No module named ‘pkg_resources'”

Setuptools removed pkg_resources in its 82.0.0 release, and Fedora 45’s python3-setuptools is newer than that. Old code that still imports it now fails at the first line, both with the system package and in a venv that pulls current setuptools:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import pkg_resources
ModuleNotFoundError: No module named 'pkg_resources'

The real fix is in the code. importlib.metadata replaces the version and entry point lookups, and importlib.resources replaces resource file access. When the import sits in a dependency you can’t change yet, pin setuptools inside the project and leave the system package alone. In a uv project, add the pin as a dependency so the lock file keeps it, because uv sync removes anything installed with uv pip behind its back.

uv add "setuptools<82"

In a plain venv, pip install "setuptools<82" does the same job. The pin covers code that imports the module at runtime, not a setup.py that imports it at build time, because builds run in an isolated environment that ignores it. The import works again but prints a deprecation warning that points at the pkg_resources migration notes. Treat that pin as a stopgap, since it freezes your build tooling at an old setuptools. The Django on Fedora guide takes this Python setup further, through a Gunicorn and Nginx deployment with SELinux enforcing.

Install Node.js and npm

Skip the plain nodejs package. On Fedora 45 it is a small metapackage that installs the Node 22 line, which only gets critical and security fixes now and reaches end of life in April 2027. Name the Node 24 packages instead, since that line keeps security support until April 2028 and both Fedora 45 and Fedora 44 ship it. The two -bin packages own the unversioned node, npm and npx commands, and they pull in the runtime as dependencies.

sudo dnf install -y nodejs24-bin nodejs24-npm-bin
node --version; npm --version; ls -l /usr/bin/node

The symlink target tells you which line owns the default node. Check it first whenever a tool reports a version you didn’t expect.

v24.18.0
11.16.0
lrwxrwxrwx. 1 root root 7 Jul 29 00:00 /usr/bin/node -> node-24

Global npm installs go to /usr/local by default, which your user can’t write to. Point npm’s prefix at ~/.local before installing any global tool, so you never need sudo npm.

npm config set prefix ~/.local
npm install -g typescript
tsc --version

Because ~/.local/bin is already on PATH, the TypeScript compiler answers straight away with no shell changes. Every later npm install -g lands in the same place.

Version 7.0.2

Need another Node line next to this one, or a newer patch release than Fedora carries? The Node.js on Fedora guide covers switching the default between lines, the dnf conflict you get when moving away from the plain nodejs package, and fnm for the releases Fedora hasn’t packaged.

Install Go from the Fedora repositories

Fedora 45 carries the current Go series and Fedora 44 stays on the previous one, so dnf is the simplest way to get a supported Go, and security updates then arrive with the rest of the system. The package does change a few of Go’s defaults, so install it and look at the environment before you write any code.

sudo dnf install -y golang
go version; go env GOPATH GOTOOLCHAIN GOBIN

Two lines here differ from upstream Go. The -X:nodwarf5 suffix marks Fedora’s build, and GOTOOLCHAIN is local, so Go never downloads a newer toolchain when a module asks for one. The last line is where go install puts the binaries it builds.

go version go1.27.1-X:nodwarf5 linux/amd64
/home/joshua/go
local
/home/joshua/go/bin

That second setting bites when you share modules. go mod init writes the full patch release into go.mod, and a teammate whose Fedora carries an older Go gets a “go.mod requires go” error instead of an automatic download. Lower the go line to the oldest release your team builds with before you commit, and remember that a Fedora 44 machine is a whole series behind Fedora 45.

The other gap is PATH. go install puts binaries in ~/go/bin, and Fedora’s shell profile doesn’t include that directory, so a tool like gopls installs fine and then isn’t found. Fedora’s ~/.bashrc sources every file in ~/.bashrc.d, which gives you a clean place for the line.

mkdir -p ~/.bashrc.d
echo 'export PATH="$PATH:$HOME/go/bin"' > ~/.bashrc.d/go.sh
source ~/.bashrc
go install golang.org/x/tools/gopls@latest
gopls version

The Go language server now runs by name, and every editor that speaks LSP can find it without a custom path. The same GOTOOLCHAIN=local rule applies to tools, so once a gopls release requires a newer Go than your Fedora ships, @latest stops installing and you pin an older gopls version instead.

golang.org/x/tools/gopls v0.23.0

One more Fedora quirk matters if you scan for vulnerabilities. That -X:nodwarf5 suffix stops govulncheck from checking the standard library, so it can report a clean result for a vulnerable build. The Go on Fedora guide shows the workaround and the go.dev tarball for when you want upstream’s defaults.

Install Rust with Fedora’s rustup

Use rustup for Rust, not the dnf rust package. Fedora’s stable repository lagged upstream by a release when this was tested, and rustup also gives you nightly, extra targets and per-project toolchain pins. The table shows what each source offered on the same day.

SourceRust version (October 2026)
Fedora 45, fedora repository1.98.1
Fedora 45, updates-testing1.99.0
Fedora 44, updates1.99.0
rustup stable channel1.99.0

Fedora 44’s updates repository had already received the newer build, while Fedora 45 still held it in testing. Fedora also packages rustup itself, so you don’t need the curl | sh installer from rustup.rs. The package gives you rustup-init, which installs the stable toolchain into your home directory.

sudo dnf install -y rustup
rustup-init -y --profile minimal
source ~/.cargo/env
rustc --version; cargo --version

Both tools report upstream’s current stable release, one ahead of Fedora 45’s own rust package. Nothing went into /usr, because the whole toolchain lives under ~/.rustup and ~/.cargo.

rustc 1.99.0 (b940084d7 2026-09-28)
cargo 1.99.0 (5f94df478 2026-08-27)

rustup-init adds a line that sources ~/.cargo/env to three shell files, so new terminals find cargo without any setup from you.

  • ~/.bashrc
  • ~/.bash_profile
  • ~/.profile

The minimal profile leaves out the editor and lint tools. Add them as components, which keeps them on the same release as the compiler.

rustup component add clippy rustfmt rust-analyzer

If something later installs the dnf rust and cargo packages too, both copies coexist. The cargo env file puts ~/.cargo/bin first in PATH, so rustup’s toolchain wins and Fedora’s sits behind it in /usr/bin. It’s still better to leave the dnf packages out, because a shell, cron job or systemd unit that never reads the env line finds /usr/bin/cargo and /usr/bin/rustc first and silently builds with Fedora’s older toolchain.

which -a cargo rustc

The first path listed for each command is the one your shell runs. If /usr/bin comes first, the env line is missing from your shell profile.

/home/joshua/.cargo/bin/cargo
/usr/bin/cargo
/home/joshua/.cargo/bin/rustc
/usr/bin/rustc

Rust is also the language that fails first without the compiler from earlier. On Fedora 44, removing GCC and building a fresh cargo new project stopped at the link step, even though rustc itself was installed fine.

   Compiling hello-rust v0.1.0 (/home/joshua/hello-rust)
error: linker `cc` not found
  |
  = note: No such file or directory (os error 2)

error: could not compile `hello-rust` (bin "hello-rust") due to 1 previous error

Reinstalling gcc fixed it with no change to the Rust toolchain. If you see this error, go back to the compiler step rather than reinstalling Rust.

Run a hello program in each language

A version string only proves a binary exists. Running real code proves the interpreter, the package layout and the linker all work together. Create a scratch directory and the Python file first.

mkdir -p ~/dev-test
vi ~/dev-test/hello.py

Paste this into the file. It reads the OS name from /etc/os-release through the standard library, so it needs no packages.

import platform

release = platform.freedesktop_os_release()
print(f"Python {platform.python_version()} on {release['NAME']} {release['VERSION_ID']}")

Then create the Node.js file, ~/dev-test/hello.js. A single script needs no package.json, so Node runs it as it is.

const os = require("node:os");

console.log(`Node.js ${process.version} on ${os.type()} ${os.release()}`);

Go wants a module, so give it its own directory before writing the source. The module path can be any name while the code stays on your machine.

mkdir -p ~/dev-test/hello-go && cd ~/dev-test/hello-go
go mod init example.com/hello
vi main.go

Paste the Go program into main.go. It prints the runtime version, so you can see the Fedora suffix from earlier.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Printf("Go %s on %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

Rust needs no file of its own, because cargo new generates a hello world. Run the five steps in order. The -C and --manifest-path options point Go and cargo at their projects, so every command works from your home directory.

  1. python3 ~/dev-test/hello.py
  2. node ~/dev-test/hello.js
  3. go -C ~/dev-test/hello-go run .
  4. cargo new ~/dev-test/hello-rust
  5. cargo run --release --manifest-path ~/dev-test/hello-rust/Cargo.toml

The Python, Node.js and Go programs print their runtime version and the platform they run on, while the generated Rust program prints Hello, world!. Cargo compiles the release build in well under a second for a project this small.

Hello programs in Python, Node.js, Go and Rust running on Fedora 45

If one of the four fails here but passed its version check, look at PATH first. which -a on the failing command shows whether a second copy is shadowing the one you installed. After any upgrade, this one line is the quickest check that every toolchain still answers.

python3 --version; uv --version; node --version; npm --version; go version; rustc --version; cargo --version; gcc --version | head -1

Each tool prints one line, in the same order as the command.

Python, uv, Node.js, npm, Go, Rust, cargo and GCC versions on a Fedora 45 dev environment

A command not found line means that tool isn’t on PATH in the current shell. Open a new terminal before you reinstall anything, since the cargo and Go PATH changes only reach shells started after you made them.

Keep each toolchain updated

dnf owns Python, uv, Node.js and Go, so sudo dnf upgrade moves them forward with the rest of Fedora. Everything you installed into your home directory updates with its own tool instead, as the table shows.

ToolchainInstalled byUpdate withLives in
Python, uv, python3.14dnfsudo dnf upgrade/usr/bin
uv-managed Pythonsuv python installuv python upgrade~/.local/share/uv/python
Node.js and npmdnfsudo dnf upgrade/usr/bin
Global npm toolsnpm install -gnpm update -g~/.local/lib/node_modules
Godnfsudo dnf upgrade/usr/lib/golang
Go toolsgo installgo install ...@latest~/go/bin
Rust toolchainrustup-initrustup update~/.rustup, ~/.cargo
rustup itselfdnf, then copied by rustup-initsudo dnf upgrade, then rustup-init -y --profile minimal~/.cargo/bin/rustup

The last row trips people up. rustup update moves your Rust toolchain forward but leaves rustup alone, and says so with “self-update is disabled for this build of rustup”. The dnf package only updates /usr/bin/rustup-init, while the rustup you run is a copy that rustup-init made in ~/.cargo/bin. Re-running rustup-init -y --profile minimal after a dnf update replaces that copy with the new rustup-init and keeps your toolchain and components. With the dnf rust package installed it also prints “error: cannot install while Rust is installed”, then carries on because -y marks that error as ignorable.

The home directory caches grow too. After the setup above, ~/.rustup held 658 MB with the three components, the uv cache 171 MB and the Go module cache 146 MB. Running uv cache clean and go clean -modcache reclaims the last two, and the next build downloads only what it needs again. For a dev box that runs containers too, Distrobox and Toolbox let you test against other distributions without touching this setup. The DNF5 cheat sheet covers rolling back an upgrade that breaks a toolchain.

Keep reading

How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Fedora How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Fedora Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Fedora Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Best Monitor Arms for a Multi-Monitor Developer Desk Programming Best Monitor Arms for a Multi-Monitor Developer Desk Best Mouse and Keyboard for Programming Programming Best Mouse and Keyboard for Programming Install GitLab CE on CentOS 7 | Fedora 39/38/37/36/35 Git Install GitLab CE on CentOS 7 | Fedora 39/38/37/36/35

Leave a Comment

Press ESC to close