In this lesson, we will learn how to create a simple desktop application using Tkinter, convert it into a standalone .exe using PyInstaller, and then package it into a proper Windows installer using Inno Setup.
This lesson starts from absolute beginner level and moves step by step toward a practical deployment workflow.
When someone says "create a Tkinter installer", they usually mean:
Create and test your Python GUI application.
Use PyInstaller to convert the Python app into a standalone executable.
Use Inno Setup to create a real installable setup file.
Suppose you built a Tkinter application on your computer. It may run because your system already has:
But another person may not have Python at all. So you cannot expect them to run your app.py directly. That is why we create a packaged application and then an installer.
Let us understand the whole process before writing code.
| Stage | Tool | Purpose |
|---|---|---|
| Build the GUI | Tkinter | Create the visual desktop application in Python. |
| Package the app | PyInstaller | Convert Python code into a standalone EXE. |
| Create the installer | Inno Setup | Create a Windows setup file that installs the application. |
A simple project folder may look like this:
tkinter-installer-demo/
│
├── app.py
├── assets/
│ └── app.ico
├── build/
├── dist/
└── installer.iss
At the beginning, you only need:
tkinter-installer-demo/
├── app.py
└── assets/
└── app.ico
We start with a very simple Tkinter application. This is the real application that the user will eventually install.
import tkinter as tk
from tkinter import messagebox
def say_hello():
name = name_var.get().strip()
if not name:
messagebox.showwarning("Missing Name", "Please enter your name.")
return
messagebox.showinfo("Welcome", f"Hello, {name}! Your app is working.")
root = tk.Tk()
root.title("Tkinter Installer Demo")
root.geometry("500x300")
root.resizable(False, False)
title_label = tk.Label(root, text="Tkinter Installer Demo", font=("Arial", 18, "bold"))
title_label.pack(pady=20)
desc_label = tk.Label(
root,
text="This desktop app will later be packaged as an installer.",
font=("Arial", 11)
)
desc_label.pack(pady=10)
name_var = tk.StringVar()
entry = tk.Entry(root, textvariable=name_var, font=("Arial", 12), width=30)
entry.pack(pady=10)
button = tk.Button(root, text="Click Me", command=say_hello, font=("Arial", 12), width=15)
button.pack(pady=20)
root.mainloop()
python app.py
If the app opens and works, then your first stage is complete.
Let us make a version that stores user settings into a file. This makes the app feel more real.
import tkinter as tk
from tkinter import messagebox
import json
import os
APP_FOLDER = os.path.join(os.path.expanduser("~"), "TkinterInstallerDemo")
SETTINGS_FILE = os.path.join(APP_FOLDER, "settings.json")
def save_settings():
username = username_var.get().strip()
theme = theme_var.get()
if not username:
messagebox.showerror("Error", "Please enter a username.")
return
os.makedirs(APP_FOLDER, exist_ok=True)
data = {
"username": username,
"theme": theme
}
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
messagebox.showinfo("Saved", f"Settings saved to:\\n{SETTINGS_FILE}")
root = tk.Tk()
root.title("Settings App")
root.geometry("420x240")
tk.Label(root, text="Username", font=("Arial", 11)).pack(pady=(20, 5))
username_var = tk.StringVar()
tk.Entry(root, textvariable=username_var, width=30, font=("Arial", 11)).pack()
tk.Label(root, text="Theme", font=("Arial", 11)).pack(pady=(15, 5))
theme_var = tk.StringVar(value="Light")
tk.OptionMenu(root, theme_var, "Light", "Dark", "Classic").pack()
tk.Button(root, text="Save Settings", command=save_settings, width=18).pack(pady=25)
root.mainloop()
Tkinter gives you the app source code, but most end users do not want to run Python files directly. They usually want a normal Windows program.
pip install pyinstaller
pyinstaller --onefile --windowed app.py
| Option | Meaning |
|---|---|
| --onefile | Create one single EXE file. |
| --windowed | Hide the console window for GUI apps. |
| app.py | Your main Python file. |
After the build finishes, look inside the dist folder.
dist/
└── app.exe
pyinstaller --onefile --windowed --icon=assets/app.ico app.py
This command adds a custom icon to your program.
When you run source code directly, normal relative paths often work. But after packaging, they may fail.
Bad direct usage:
logo_path = "assets/logo.png"
Better approach:
import os
import sys
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except AttributeError:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
Then use it like this:
logo_path = resource_path("assets/logo.png")
pyinstaller --onefile --windowed --add-data "assets;assets" app.py
| Mode | What you get | When to use |
|---|---|---|
| --onefile | One EXE | Easy distribution |
| --onedir | A folder with many files | Easier debugging, sometimes more reliable |
After PyInstaller gives us a standalone EXE, we use Inno Setup to create a proper installer.
[Setup]
AppName=Tkinter Installer Demo
AppVersion=1.0
DefaultDirName={autopf}\TkinterInstallerDemo
DefaultGroupName=Tkinter Installer Demo
OutputDir=output
OutputBaseFilename=TkinterInstallerDemoSetup
Compression=lzma
SolidCompression=yes
SetupIconFile=assets\app.ico
[Files]
Source: "dist\app.exe"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\Tkinter Installer Demo"; Filename: "{app}\app.exe"
Name: "{autodesktop}\Tkinter Installer Demo"; Filename: "{app}\app.exe"; Tasks: desktopicon
[Tasks]
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
[Run]
Filename: "{app}\app.exe"; Description: "Launch Tkinter Installer Demo"; Flags: nowait postinstall skipifsilent
This section defines installer-level details such as application name, version, output file, default installation folder, and compression options.
This section tells Inno Setup which files to copy into the installation directory.
This section creates Start Menu and desktop shortcuts.
This defines optional items such as the desktop shortcut checkbox.
This lets the user run the program immediately after installation finishes.
output/
└── TkinterInstallerDemoSetup.exe
import tkinter as tk
from tkinter import messagebox
import json
import os
import sys
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except AttributeError:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
APP_FOLDER = os.path.join(os.path.expanduser("~"), "TkinterInstallerDemo")
SETTINGS_FILE = os.path.join(APP_FOLDER, "settings.json")
def save_settings():
username = username_var.get().strip()
theme = theme_var.get()
if not username:
messagebox.showerror("Error", "Please enter a username.")
return
os.makedirs(APP_FOLDER, exist_ok=True)
data = {
"username": username,
"theme": theme
}
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
messagebox.showinfo("Saved", f"Settings saved to:\\n{SETTINGS_FILE}")
root = tk.Tk()
root.title("Tkinter Installer Demo")
root.geometry("500x320")
root.resizable(False, False)
title_label = tk.Label(root, text="Tkinter Installer Demo", font=("Arial", 18, "bold"))
title_label.pack(pady=20)
tk.Label(root, text="Username", font=("Arial", 11)).pack(pady=(6, 5))
username_var = tk.StringVar()
tk.Entry(root, textvariable=username_var, width=30, font=("Arial", 11)).pack()
tk.Label(root, text="Theme", font=("Arial", 11)).pack(pady=(15, 5))
theme_var = tk.StringVar(value="Light")
tk.OptionMenu(root, theme_var, "Light", "Dark", "Classic").pack()
tk.Button(root, text="Save Settings", command=save_settings, width=18).pack(pady=25)
root.mainloop()
pyinstaller --onefile --windowed --icon=assets/app.ico app.py
[Setup]
AppName=Tkinter Installer Demo
AppVersion=1.0
DefaultDirName={autopf}\TkinterInstallerDemo
DefaultGroupName=Tkinter Installer Demo
OutputDir=output
OutputBaseFilename=TkinterInstallerDemoSetup
Compression=lzma
SolidCompression=yes
SetupIconFile=assets\app.ico
[Files]
Source: "dist\app.exe"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\Tkinter Installer Demo"; Filename: "{app}\app.exe"
Name: "{autodesktop}\Tkinter Installer Demo"; Filename: "{app}\app.exe"; Tasks: desktopicon
[Tasks]
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
[Run]
Filename: "{app}\app.exe"; Description: "Launch Tkinter Installer Demo"; Flags: nowait postinstall skipifsilent
Cause: hidden error, missing dependency, or file path problem.
Fix: build without --windowed first so you can see the error.
pyinstaller --onefile app.py
Cause: relative path fails after packaging.
Fix: use resource_path() and --add-data.
Cause: the packaged EXE itself was already broken.
Fix: run the EXE directly from the dist folder before creating the installer.
This can sometimes happen with packaged executables. Test carefully, avoid suspicious bundling behavior, and distribute only well-tested builds.
Check whether your application has permission to write files to the chosen folder. Saving inside the user's home directory is usually a safer choice than saving inside the program installation folder.
Create a Tkinter app with:
Convert it into an EXE using:
pyinstaller --onefile --windowed app.py
Create an Inno Setup script that:
Add an icon and version number to both the application and the installer.
Modify the settings app so that it stores:
python app.py
pip install pyinstaller
pyinstaller --onefile --windowed app.py
pyinstaller --onefile --windowed --icon=assets/app.ico app.py
pyinstaller --onefile --windowed --add-data "assets;assets" app.py
Because the target computer may not have Python or the required libraries installed.
Because GUI apps usually do not need a console window.
Because PyInstaller creates the runnable application, while Inno Setup creates the installation experience.
It helps your packaged application find bundled files correctly.
Build a Tkinter installer project for a small app called Student Notes Manager.
A Tkinter installer is not a single tool or a single button. It is a process.
Once you understand this workflow, you can build installers for many kinds of Python desktop apps, not just Tkinter.