Skip to content

Commit af3ccd7

Browse files
feat: add HOST_OVERRIDE env var for reverse proxy support (fixes #51) (#76)
1 parent c75e717 commit af3ccd7

File tree

10 files changed

+46
-6
lines changed

10 files changed

+46
-6
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to portracker will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- **Reverse Proxy Support**: New `HOST_OVERRIDE` environment variable allows specifying the hostname used in port links when running behind a reverse proxy (fixes #51)
10+
711
### UI
812

913
- **Favicon**: Dark mode support - favicon now adapts to system theme (black on light, white on dark)

backend/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const RESP_TTL_PORTS = parseInt(process.env.ENDPOINT_CACHE_PORTS_TTL_MS || '3000
3535
const PORT_SUGGEST_MIN = parseInt(process.env.GENERATE_PORT_MIN || '30000', 10);
3636
const PORT_SUGGEST_MAX = parseInt(process.env.GENERATE_PORT_MAX || '60999', 10);
3737
const PORT_SUGGEST_BIND_HOST = process.env.GENERATE_PORT_BIND_HOST || '0.0.0.0';
38+
const HOST_OVERRIDE = process.env.HOST_OVERRIDE || '';
3839
const PORT_SUGGEST_MAX_RANDOM_ATTEMPTS = 60;
3940

4041
if (isAuthEnabled()) {
@@ -2450,6 +2451,12 @@ app.get("/api/health", (req, res) => {
24502451
}
24512452
});
24522453

2454+
app.get("/api/config", (req, res) => {
2455+
res.json({
2456+
hostOverride: HOST_OVERRIDE || null
2457+
});
2458+
});
2459+
24532460
app.get('/api/changelog', (req, res) => {
24542461
logger.debug("Changelog requested");
24552462
try {

docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ services:
6161
# If not set, a random secret is generated (sessions won't persist across restarts)
6262
# - SESSION_SECRET=your-random-secret-here-change-this
6363

64+
# REVERSE PROXY SUPPORT (Optional)
65+
# When running behind a reverse proxy, port links will use the proxy hostname
66+
# Set HOST_OVERRIDE to your actual server hostname so port links work correctly
67+
# Example: Your server is server.local, but you access Portracker via proxy.domain.com
68+
# Set HOST_OVERRIDE=server.local so port links point to server.local:PORT instead
69+
# - HOST_OVERRIDE=your-server-hostname
70+
6471
# PERFORMANCE SETTINGS (Optional)
6572
# Cache duration - increase for better performance, decrease for fresher data
6673
# - CACHE_TIMEOUT_MS=60000

frontend/src/App.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default function App() {
6161
const [groups, setGroups] = useState([]);
6262
const [loading, setLoading] = useState(true);
6363
const [error, setError] = useState(null);
64+
const [hostOverride, setHostOverride] = useState(null);
6465

6566
const [noteModalOpen, setNoteModalOpen] = useState(false);
6667
const [modalSrvId, setModalSrvId] = useState("");
@@ -745,6 +746,13 @@ export default function App() {
745746
fetchAll();
746747
}, [fetchAll, auth.loading, auth.authEnabled, auth.authenticated]);
747748

749+
useEffect(() => {
750+
fetch("/api/config")
751+
.then(res => res.ok ? res.json() : null)
752+
.then(data => { if (data?.hostOverride) setHostOverride(data.hostOverride); })
753+
.catch(() => {});
754+
}, []);
755+
748756
useEffect(() => {
749757
try {
750758
const params = new URLSearchParams(window.location.search);
@@ -1761,7 +1769,7 @@ export default function App() {
17611769
p.host_ip === "127.0.0.1" ||
17621770
p.host_ip === "[::]" ||
17631771
p.host_ip === "[::1]")) {
1764-
hostForCopy = window.location.hostname;
1772+
hostForCopy = hostOverride || window.location.hostname;
17651773
}
17661774
else if (
17671775
server.id !== "local" &&
@@ -1809,6 +1817,7 @@ export default function App() {
18091817
}
18101818
}}
18111819
serverUrl={server.url}
1820+
hostOverride={hostOverride}
18121821
platformName={server.platformName}
18131822
systemInfo={server.systemInfo}
18141823
vms={server.vms}
@@ -1925,6 +1934,7 @@ export default function App() {
19251934
onAdd={addServer}
19261935
onDelete={deleteServer}
19271936
loading={loading}
1937+
hostOverride={hostOverride}
19281938
/>
19291939
}
19301940
>

frontend/src/components/layout/Sidebar.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const ServerCard = React.memo(function ServerCard({
6161
onSelect,
6262
onEdit,
6363
onDelete,
64+
hostOverride,
6465
children,
6566
}) {
6667
const name = server.server || "Unknown Server";
@@ -76,7 +77,7 @@ const ServerCard = React.memo(function ServerCard({
7677

7778
const getHostDisplay = () => {
7879
if (!server.url) {
79-
return window.location.host || "localhost";
80+
return hostOverride || window.location.host || "localhost";
8081
}
8182

8283
try {
@@ -217,6 +218,7 @@ export function Sidebar({
217218
onAdd,
218219
onDelete,
219220
loading,
221+
hostOverride,
220222
}) {
221223
const [mode, setMode] = useState("list");
222224
const [form, setForm] = useState({
@@ -500,6 +502,7 @@ export function Sidebar({
500502
onSelect={onSelect}
501503
onEdit={setMode}
502504
onDelete={setServerToDelete}
505+
hostOverride={hostOverride}
503506
>
504507
{children.length > 0 && level < maxIndentLevel && (
505508
<div className="mt-4 pt-4 border-t border-slate-200 dark:border-slate-700/50 space-y-3">

frontend/src/components/server/PortCard.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function PortCardComponent({
5050
onRename,
5151
serverId,
5252
serverUrl,
53+
hostOverride,
5354
forceOpenDetails,
5455
notifyOpenDetails,
5556
notifyCloseDetails,
@@ -67,7 +68,7 @@ function PortCardComponent({
6768
let hostForUi;
6869
if (port.host_ip === "0.0.0.0" || port.host_ip === "127.0.0.1") {
6970
if (serverId === "local") {
70-
hostForUi = window.location.hostname;
71+
hostForUi = hostOverride || window.location.hostname;
7172
} else if (serverUrl) {
7273
try {
7374
hostForUi = new URL(serverUrl).hostname;

frontend/src/components/server/PortGridItem.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function PortGridItem({
4949
port,
5050
serverId,
5151
serverUrl,
52+
hostOverride,
5253
searchTerm,
5354
actionFeedback,
5455
onCopy,
@@ -72,7 +73,7 @@ export function PortGridItem({
7273
let hostForUi;
7374
if (port.host_ip === "0.0.0.0" || port.host_ip === "127.0.0.1") {
7475
if (serverId === "local") {
75-
hostForUi = window.location.hostname;
76+
hostForUi = hostOverride || window.location.hostname;
7677
} else if (serverUrl) {
7778
try {
7879
hostForUi = new URL(serverUrl).hostname;

frontend/src/components/server/PortTable.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function PortTable({
1111
ports,
1212
serverId,
1313
serverUrl,
14+
hostOverride,
1415
searchTerm,
1516
actionFeedback,
1617
onCopy,
@@ -153,6 +154,7 @@ export function PortTable({
153154
port={port}
154155
serverId={serverId}
155156
serverUrl={serverUrl}
157+
hostOverride={hostOverride}
156158
searchTerm={searchTerm}
157159
actionFeedback={actionFeedback}
158160
onCopy={onCopy}

frontend/src/components/server/PortTableRow.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function PortTableRowComponent({
4444
port,
4545
serverId,
4646
serverUrl,
47+
hostOverride,
4748
searchTerm,
4849
actionFeedback,
4950
onCopy,
@@ -67,7 +68,7 @@ function PortTableRowComponent({
6768
let hostForUi;
6869
if (port.host_ip === "0.0.0.0" || port.host_ip === "127.0.0.1") {
6970
if (serverId === "local") {
70-
hostForUi = window.location.hostname;
71+
hostForUi = hostOverride || window.location.hostname;
7172
} else if (serverUrl) {
7273
try {
7374
hostForUi = new URL(serverUrl).hostname;

frontend/src/components/server/ServerSection.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function ServerSectionComponent({
6767
onRename,
6868
onCopy,
6969
serverUrl,
70+
hostOverride,
7071
platformName,
7172
systemInfo,
7273
vms,
@@ -257,7 +258,7 @@ function ServerSectionComponent({
257258

258259
const getHostDisplay = () => {
259260
if (!serverUrl) {
260-
return window.location.host || "localhost";
261+
return hostOverride || window.location.host || "localhost";
261262
}
262263

263264
try {
@@ -777,6 +778,7 @@ function ServerSectionComponent({
777778
onRename={onRename}
778779
serverId={id}
779780
serverUrl={serverUrl}
781+
hostOverride={hostOverride}
780782
forceOpenDetails={deepLinkContainerId && port.container_id === deepLinkContainerId}
781783
notifyOpenDetails={(cid) => onOpenContainerDetails && onOpenContainerDetails(cid)}
782784
notifyCloseDetails={() => onCloseContainerDetails && onCloseContainerDetails()}
@@ -848,6 +850,7 @@ function ServerSectionComponent({
848850
onRename={onRename}
849851
serverId={id}
850852
serverUrl={serverUrl}
853+
hostOverride={hostOverride}
851854
forceOpenDetails={deepLinkContainerId && port.container_id === deepLinkContainerId}
852855
notifyOpenDetails={(cid) => onOpenContainerDetails && onOpenContainerDetails(cid)}
853856
notifyCloseDetails={() => onCloseContainerDetails && onCloseContainerDetails()}
@@ -864,6 +867,7 @@ function ServerSectionComponent({
864867
ports={portsToDisplay}
865868
serverId={id}
866869
serverUrl={serverUrl}
870+
hostOverride={hostOverride}
867871
searchTerm={searchTerm}
868872
actionFeedback={actionFeedback}
869873
onCopy={onCopy}

0 commit comments

Comments
 (0)