Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
72a7a45
Personal main updates (#480)
Lokowitz Dec 3, 2025
83875a8
fix zod error (#481)
Lokowitz Dec 3, 2025
f121536
Merge branch 'fosrl:main' into main
Lokowitz Dec 6, 2025
2cad19a
Merge branch 'fosrl:main' into main
Lokowitz Dec 13, 2025
4055403
Merge branch 'fosrl:main' into main
Lokowitz Dec 14, 2025
9d63762
Merge branch 'fosrl:main' into main
Lokowitz Dec 21, 2025
b567d63
Merge branch 'fosrl:main' into main
Lokowitz Dec 31, 2025
c1c7e46
Merge branch 'fosrl:main' into main
Lokowitz Jan 3, 2026
40f2262
Merge pull request #2309 from fosrl/dev
oschwartz10612 Jan 23, 2026
1d17165
Merge branch 'fosrl:main' into main
Lokowitz Jan 24, 2026
71e09a0
Merge branch 'fosrl:main' into main
Lokowitz Jan 25, 2026
21beb8c
Update build args to include BUILD=enterprise
Lokowitz Jan 25, 2026
c8f265a
Bump actions/checkout from 4 to 6 (#493)
dependabot[bot] Jan 25, 2026
e8ac0dd
Update hybrid.ts
Lokowitz Jan 26, 2026
593a09c
Update dev-image.yml
Lokowitz Jan 26, 2026
3dd6c9b
Update dev-image.yml
Lokowitz Jan 26, 2026
cb569ff
Properly insert PANGOLIN_SETUP_TOKEN into db
oschwartz10612 Jan 28, 2026
5dda8c3
fix(i18n): correct German translation strings
MoweME Jan 29, 2026
b0566d3
fix(i18n): correct German site terminology
MoweME Jan 29, 2026
9603fea
Merge branch 'fosrl:main' into main
Lokowitz Jan 31, 2026
88620ab
move to dhi
Lokowitz Feb 1, 2026
38c3bed
add entrypoint
Lokowitz Feb 1, 2026
2ce8592
make executable
Lokowitz Feb 1, 2026
825bb16
fix entrypoint
Lokowitz Feb 1, 2026
51569ff
added healthcheck
Lokowitz Feb 1, 2026
0bc3143
try
Lokowitz Feb 1, 2026
4aed48e
update
Lokowitz Feb 2, 2026
7e63740
update
Lokowitz Feb 2, 2026
61c9887
Merge remote-tracking branch 'origin/main' into test-dhi
Lokowitz Feb 2, 2026
842b545
fix build
Lokowitz Feb 2, 2026
3f076bf
update node
Lokowitz Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ dist
migrations/
config/
build.ts
tsconfig.json
tsconfig.json
drizzle.config.ts
166 changes: 166 additions & 0 deletions .github/workflows/dev-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: Create Dev-Image

on:
pull_request_target:
branches:
- main
- dev
types:
- opened
- synchronize
- reopened

concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
TAG_URL: https://hub.docker.com/r/${{ vars.DOCKER_HUB_REPO }}/tags
TAG: ${{ vars.DOCKER_HUB_REPO }}:dev-pr${{ github.event.pull_request.number }}
TAG_PG: ${{ vars.DOCKER_HUB_REPO }}:postgresql-dev-pr${{ github.event.pull_request.number }}

jobs:
build-w:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ubuntu-latest
environment: build-dev

steps:
- name: Checkout PR code
uses: actions/checkout@v6
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image SQLITE
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ env.TAG }}
build-args: |
DATABASE=sqlite
BUILD=enterprise
cache-from: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache
cache-to: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache,mode=max

- name: Build and push Docker image PG
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ env.TAG_PG }}
build-args: |
DATABASE=sqlite
BUILD=enterprise
cache-from: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache-pg
cache-to: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache-pg,mode=max

- uses: actions/github-script@v8
with:
script: |
const repoUrl = process.env.TAG_URL;
const tag = process.env.TAG;
const tagPg = process.env.TAG_PG;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `> [!WARNING]
> This image may contain unchecked and breaking changes. Only use on own risk.

👋 Thanks for your PR!
Dev images for this PR are now available on [docker hub](${repoUrl}):

**SQLITE Image:**
\`\`\`
${tag}
\`\`\`

**Postgresql Image:**
\`\`\`
${tagPg}
\`\`\``
})
build-wo:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest

steps:
- name: Checkout PR code
uses: actions/checkout@v6
with:
ref: refs/pull/${{github.event.pull_request.number}}/merge

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image SQLITE
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ env.TAG }}
build-args: |
DATABASE=sqlite
BUILD=enterprise
cache-from: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache
cache-to: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache,mode=max

- name: Build and push Docker image PG
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ env.TAG_PG }}
build-args: |
DATABASE=sqlite
BUILD=enterprise
cache-from: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache-pg
cache-to: type=registry,ref=${{ vars.DOCKER_HUB_REPO }}:buildcache-pg,mode=max

- uses: actions/github-script@v8
with:
script: |
const repoUrl = process.env.TAG_URL;
const tag = process.env.TAG;
const tagPg = process.env.TAG_PG;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `> [!WARNING]
> This image may contain unchecked and breaking changes. Only use on own risk.

👋 Thanks for your PR!
Dev images for this PR are now available on [docker hub](${repoUrl}):

**SQLITE Image:**
\`\`\`
${tag}
\`\`\`

**Postgresql Image:**
\`\`\`
${tagPg}
\`\`\``
})

12 changes: 3 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
node-version: '24'

- name: Copy config file
run: cp config/config.example.yml config/config.yml
Expand All @@ -34,10 +34,10 @@ jobs:
run: npm run set:oss

- name: Generate database migrations
run: npm run db:sqlite:generate
run: npm run db:generate

- name: Apply database migrations
run: npm run db:sqlite:push
run: npm run db:sqlite

- name: Test with tsc
run: npx tsc --noEmit
Expand All @@ -64,9 +64,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Copy config file
run: cp config/config.example.yml config/config.yml

- name: Build Docker image sqlite
run: make dev-build-sqlite

Expand All @@ -76,8 +73,5 @@ jobs:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Copy config file
run: cp config/config.example.yml config/config.yml

- name: Build Docker image pg
run: make dev-build-pg
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ dynamic/
scratch/
tsconfig.json
hydrateSaas.ts
CLAUDE.md
CLAUDE.md
drizzle.config.ts
76 changes: 44 additions & 32 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,67 +1,76 @@
FROM node:24-alpine AS builder
FROM dhi.io/node:24-alpine3.23-dev AS builder

WORKDIR /app

ARG BUILD=oss
ARG DATABASE=sqlite

RUN apk add --no-cache python3 make g++
# Install build dependencies
RUN apk add --no-cache \
g++ \
make \
python3

# COPY package.json package-lock.json ./
# Copy dependency files first for better caching
COPY package*.json ./
RUN npm ci

# Copy source files
COPY . .

# Build application
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
npm run set:$DATABASE && \
npm run set:$BUILD && \
npm run db:$DATABASE:generate && \
npm run build:$DATABASE && \
npm run build:cli
npm run db:generate && \
npm run build && \
npm run build:cli && \
test -f dist/server.mjs

# test to make sure the build output is there and error if not
RUN test -f dist/server.mjs
FROM dhi.io/node:24-alpine3.23-dev AS runner

# Prune dev dependencies and clean up to prepare for copy to runner
RUN npm prune --omit=dev && npm cache clean --force
WORKDIR /app

# Install runtime dependencies and install production node_modules
RUN apk add --no-cache \
g++ \
make \
python3 \
tzdata
Comment on lines +35 to +39

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The builder stage and this runner stage both install the same set of build dependencies (g++, make, python3) using apk add. This redundancy increases build time and image size. To optimize this, you could create a common base stage that installs these dependencies once, and then have both builder and runner stages use it via FROM <base_stage_name>. This will leverage Docker's layer caching more effectively.

Example:

FROM dhi.io/node:24-alpine3.23-dev AS base
# Install build dependencies once
RUN apk add --no-cache \
    g++ \
    make \
    python3

FROM base AS builder
# ... rest of builder stage, no need for apk add

FROM base AS runner
# ... rest of runner stage, no need for apk add


COPY package*.json ./
RUN npm ci --omit=dev && \
npm cache clean --force

FROM node:24-alpine AS runner
FROM dhi.io/node:24-alpine3.23

# OCI Image Labels - Build Args for dynamic values
ARG VERSION="dev"
ARG REVISION=""
ARG CREATED=""
ARG LICENSE="AGPL-3.0"

# Derive title and description based on BUILD type
ARG IMAGE_TITLE="Pangolin"
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"

WORKDIR /app

# Only curl and tzdata needed at runtime - no build tools!
RUN apk add --no-cache curl tzdata

# Copy pre-built node_modules from builder (already pruned to production only)
# This includes the compiled native modules like better-sqlite3
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/server/migrations ./dist/init
COPY --from=builder /app/package.json ./package.json
# Copy pre-built node_modules and timezone data from runner stage
COPY --from=runner /app/node_modules ./node_modules
COPY --from=runner /usr/share/zoneinfo /usr/share/zoneinfo

COPY ./cli/wrapper.sh /usr/local/bin/pangctl
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
# Copy built artifacts from builder stage
COPY --chown=node:node --from=builder /app/.next/standalone ./
COPY --chown=node:node --from=builder /app/.next/static ./.next/static
COPY --chown=node:node --from=builder /app/dist ./dist
COPY --chown=node:node --from=builder /app/server/migrations ./dist/init

COPY server/db/names.json ./dist/names.json
COPY server/db/ios_models.json ./dist/ios_models.json
COPY server/db/mac_models.json ./dist/mac_models.json
COPY public ./public
COPY --chown=node:node --chmod=+x ./cli/wrapper.sh /usr/local/bin/pangctl
COPY --chown=node:node server/db/names.json server/db/*_models.json ./dist/
COPY --chown=node:node public ./public
COPY --chown=node:node --chmod=+x entrypoint.mjs /entrypoint.mjs
COPY --chown=node:node --chmod=+x healthcheck.mjs /healthcheck.mjs

# OCI Image Labels
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
org.opencontainers.image.url="https://github.com/fosrl/pangolin" \
org.opencontainers.image.documentation="https://docs.pangolin.net" \
Expand All @@ -73,4 +82,7 @@ LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
org.opencontainers.image.revision="${REVISION}" \
org.opencontainers.image.created="${CREATED}"

CMD ["npm", "run", "start"]
# Run as non-root user
USER node

ENTRYPOINT ["node", "/entrypoint.mjs"]
Loading