Skip to content

Commit 25d8b9d

Browse files
committed
Upgrade deployment scripts
1 parent 045db41 commit 25d8b9d

14 files changed

Lines changed: 296 additions & 122 deletions

File tree

.github/workflows/build-container.yml

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,17 @@ jobs:
2727

2828
- name: Set up environment variables
2929
run: |
30-
echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
31-
echo "repository_name=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV
32-
echo "repository_name_lower=$(echo ${{ github.repository }} | cut -d '/' -f 2 | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
33-
echo "org_name=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV
30+
echo "IMAGE=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
31+
repo_name="$(echo ${{ github.repository }} | cut -d '/' -f 2)"
3432
35-
# Set SERVICE_LABEL: derive from GITHUB_REPOSITORY (replace dots with dashes)
36-
echo "SERVICE_LABEL=$(echo ${{ github.repository }} | cut -d '/' -f 2 | tr '.' '-')" >> $GITHUB_ENV
33+
# Set SERVICE: derive from repo name (replace dots with dashes)
34+
echo "SERVICE=$(echo $repo_name | tr '[:upper:]' '[:lower:]' | tr '.' '-')" >> $GITHUB_ENV
3735
3836
# Set KAMAL_DEPLOY_HOST: use secret if available, otherwise use repository name
3937
if [ -n "${{ secrets.KAMAL_DEPLOY_HOST }}" ]; then
4038
DEPLOY_HOST="${{ secrets.KAMAL_DEPLOY_HOST }}"
4139
else
42-
DEPLOY_HOST="$(echo ${{ github.repository }} | cut -d '/' -f 2)"
40+
DEPLOY_HOST="$repo_name"
4341
fi
4442
4543
# Validate KAMAL_DEPLOY_HOST contains at least one '.'
@@ -56,7 +54,7 @@ jobs:
5654
KAMAL_DEPLOY_IP: ${{ secrets.KAMAL_DEPLOY_IP }}
5755
if: env.KAMAL_DEPLOY_IP != null
5856
run: |
59-
sed -i 's#<ContainerLabel Include="service" Value="my-app" />#<ContainerLabel Include="service" Value="${{ env.repository_name_lower }}" />#g' TechStacks/TechStacks.csproj
57+
sed -i 's#<ContainerLabel Include="service" Value="my-app" />#<ContainerLabel Include="service" Value="${{ env.SERVICE }}" />#g' TechStacks/TechStacks.csproj
6058
6159
- name: Check for Client directory and package.json
6260
id: check_client
@@ -83,20 +81,6 @@ jobs:
8381
working-directory: ./TechStacks.Client
8482
run: npm run build
8583

86-
- name: Install x tool
87-
run: dotnet tool install -g x
88-
89-
- name: Apply Production AppSettings
90-
env:
91-
APPSETTINGS_PATCH: ${{ secrets.APPSETTINGS_PATCH }}
92-
if: env.APPSETTINGS_PATCH != null
93-
working-directory: ./TechStacks
94-
run: |
95-
cat <<EOF >> appsettings.json.patch
96-
${{ secrets.APPSETTINGS_PATCH }}
97-
EOF
98-
x patch appsettings.json.patch
99-
10084
- name: Login to GitHub Container Registry
10185
uses: docker/login-action@v3
10286
with:
@@ -113,12 +97,12 @@ jobs:
11397
env:
11498
SERVICESTACK_LICENSE: ${{ secrets.SERVICESTACK_LICENSE }}
11599
KAMAL_DEPLOY_HOST: ${{ env.KAMAL_DEPLOY_HOST }}
116-
SERVICE_LABEL: ${{ env.SERVICE_LABEL }}
100+
SERVICE: ${{ env.SERVICE }}
117101
run: |
118102
docker build \
119103
--build-arg SERVICESTACK_LICENSE="$SERVICESTACK_LICENSE" \
120104
--build-arg KAMAL_DEPLOY_HOST="$KAMAL_DEPLOY_HOST" \
121-
--build-arg SERVICE_LABEL="$SERVICE_LABEL" \
122-
-t ghcr.io/${{ env.image_repository_name }}:latest \
105+
--build-arg SERVICE="$SERVICE" \
106+
-t ${{ env.IMAGE }}:latest \
123107
-f Dockerfile .
124-
docker push ghcr.io/${{ env.image_repository_name }}:latest
108+
docker push ${{ env.IMAGE }}:latest

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ jobs:
1818
with:
1919
dotnet-version: 10.0.x
2020

21-
- name: Restore NuGet packages (use repo NuGet.config)
22-
run: dotnet restore TechStacks.slnx --configfile ./NuGet.Config
21+
- name: Restore NuGet packages
22+
run: dotnet restore TechStacks.slnx
2323

2424
# If your feed requires authentication, enable and configure the step below.
2525
# This example uses a Personal Access Token stored in secrets.NUGET_API_KEY.
@@ -40,7 +40,7 @@ jobs:
4040

4141
# - name: test
4242
# run: |
43-
# dotnet test --no-restore
43+
# dotnet test
4444
# if [ $? -eq 0 ]; then
4545
# echo TESTS PASSED
4646
# else

.github/workflows/release.yml

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ on:
1515
env:
1616
DOCKER_BUILDKIT: 1
1717
SERVICESTACK_LICENSE: ${{ secrets.SERVICESTACK_LICENSE }}
18+
APPSETTINGS_JSON: ${{ secrets.APPSETTINGS_JSON }}
1819
KAMAL_DEPLOY_IP: ${{ secrets.KAMAL_DEPLOY_IP }}
1920
KAMAL_DEPLOY_HOST: ${{ secrets.KAMAL_DEPLOY_HOST }}
20-
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
2121
KAMAL_REGISTRY_USERNAME: ${{ github.actor }}
2222
KAMAL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
23+
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
2324

2425
jobs:
2526
release:
@@ -29,12 +30,18 @@ jobs:
2930
- name: Checkout code
3031
uses: actions/checkout@v5
3132

33+
- name: Encode APPSETTINGS_JSON for runtime
34+
if: env.APPSETTINGS_JSON != null
35+
run: |
36+
# Base64 encode to avoid shell/YAML quoting issues; keep as a single env var.
37+
b64=$(printf '%s' "$APPSETTINGS_JSON" | base64 -w0)
38+
echo "APPSETTINGS_JSON_BASE64=$b64" >> $GITHUB_ENV
39+
3240
- name: Set up environment variables
3341
run: |
34-
echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
35-
echo "repository_name=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV
36-
echo "repository_name_lower=$(echo ${{ github.repository }} | cut -d '/' -f 2 | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
37-
echo "org_name=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV
42+
echo "IMAGE=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
43+
repo_name="$(echo ${{ github.repository }} | cut -d '/' -f 2)"
44+
echo "SERVICE=$(echo $repo_name | tr '[:upper:]' '[:lower:]' | tr '.' '-')" >> $GITHUB_ENV
3845
if find . -maxdepth 2 -type f -name "Configure.Db.Migrations.cs" | grep -q .; then
3946
echo "HAS_MIGRATIONS=true" >> $GITHUB_ENV
4047
else
@@ -74,25 +81,22 @@ jobs:
7481
- name: Ensure directories exist with correct permissions
7582
run: |
7683
echo "Creating directories with correct permissions"
77-
kamal server exec "mkdir -p /opt/docker/${{ env.repository_name }}/App_Data /opt/docker/${{ env.repository_name }}/initdb.d /opt/docker/${{ env.repository_name }}/postgres"
84+
kamal server exec "mkdir -p /opt/docker/${{ env.SERVICE }}/App_Data /opt/docker/${{ env.SERVICE }}/initdb.d"
7885
7986
echo "Setting app file permissions"
80-
kamal server exec "chown -R 1654:1654 /opt/docker/${{ env.repository_name }}/App_Data /opt/docker/${{ env.repository_name }}/initdb.d"
81-
82-
echo "Setting postgres file permissions"
83-
kamal server exec "chown -R 999:999 /opt/docker/${{ env.repository_name }}/postgres"
87+
kamal server exec "chown -R 1654:1654 /opt/docker/${{ env.SERVICE }}/App_Data /opt/docker/${{ env.SERVICE }}/initdb.d"
8488
8589
- name: Check if first run and execute kamal app boot if necessary
8690
run: |
87-
FIRST_RUN_FILE="~/first-run/${{ env.repository_name }}"
91+
FIRST_RUN_FILE="~/first-run/${{ env.SERVICE }}"
8892
if ! kamal server exec -q "test -f $FIRST_RUN_FILE"; then
8993
kamal server exec -q "mkdir -p ~/first-run && touch $FIRST_RUN_FILE" || true
9094
9195
if [ -n "${{env.INIT_DB_SQL}}" ]; then
9296
echo "Initializing DB with INIT_DB_SQL secret..."
9397
# Save the SQL content to a temporary file
9498
echo "${{ env.INIT_DB_SQL }}" > init-db.sql
95-
cat init-db.sql | kamal server exec -i "cat > /opt/docker/${{ env.repository_name }}/initdb.d/${{ env.repository_name }}.sql" && rm init-db.sql || true
99+
cat init-db.sql | kamal server exec -i "cat > /opt/docker/${{ env.SERVICE }}/initdb.d/${{ env.SERVICE }}.sql" && rm init-db.sql || true
96100
fi
97101
# Start all kamal accessories
98102
kamal accessory boot all || true
@@ -105,13 +109,13 @@ jobs:
105109
106110
- name: Verify file permissions before deploy
107111
run: |
108-
kamal server exec --no-interactive "chown -R 1654:1654 /opt/docker/${{ env.repository_name }}/App_Data /opt/docker/${{ env.repository_name }}/initdb.d && chown -R 999:999 /opt/docker/${{ env.repository_name }}/postgres"
112+
kamal server exec --no-interactive "chown -R 1654:1654 /opt/docker/${{ env.SERVICE }}/App_Data /opt/docker/${{ env.SERVICE }}/initdb.d"
109113
110114
- name: Deploy with Kamal
111115
run: |
112116
kamal lock release -v
113117
kamal server exec --no-interactive 'echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin'
114-
kamal server exec --no-interactive 'docker pull ghcr.io/${{ env.image_repository_name }}:latest'
118+
kamal server exec --no-interactive 'docker pull ${{ env.IMAGE }}:latest'
115119
kamal deploy -P --version latest
116120
117121
- name: Migration

.kamal/secrets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
77
KAMAL_REGISTRY_USERNAME=$KAMAL_REGISTRY_USERNAME
88
SERVICESTACK_LICENSE=$SERVICESTACK_LICENSE
9+
APPSETTINGS_JSON_BASE64=$APPSETTINGS_JSON_BASE64
910
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
1011

1112
# Option 2: Read secrets via a command

Dockerfile

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Build arguments
44
ARG KAMAL_DEPLOY_HOST
55
ARG SERVICESTACK_LICENSE
6-
ARG SERVICE_LABEL
6+
ARG SERVICE
77

88
# 1. Build .NET app + Node.js apps
99
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS dotnet-build
@@ -22,7 +22,6 @@ RUN apt-get update \
2222

2323
# Copy solution and projects
2424
COPY TechStacks.slnx ./
25-
COPY NuGet.Config ./
2625
COPY TechStacks ./TechStacks
2726
COPY TechStacks.ServiceInterface ./TechStacks.ServiceInterface
2827
COPY TechStacks.ServiceModel ./TechStacks.ServiceModel
@@ -31,17 +30,17 @@ COPY TechStacks.ServiceModel ./TechStacks.ServiceModel
3130
WORKDIR /src/TechStacks
3231

3332
# Download tailwindcss binary directly (avoiding sudo requirement in postinstall.js)
34-
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \
35-
&& chmod +x tailwindcss-linux-x64 \
36-
&& mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
33+
RUN curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 \
34+
-o /usr/local/bin/tailwindcss \
35+
&& chmod +x /usr/local/bin/tailwindcss
3736
RUN npm run ui:build
3837

3938
# Build Next.js app
4039
WORKDIR /src/TechStacks.Client
41-
COPY TechStacks.Client/package*.json ./
40+
COPY TechStacks.Client/package*.json TechStacks.Client/postinstall.mjs ./
4241
RUN npm ci
4342
COPY TechStacks.Client/ ./
44-
RUN npm run build:prod
43+
RUN npm run build
4544

4645
# Restore and publish .NET app
4746
WORKDIR /src
@@ -52,13 +51,13 @@ RUN dotnet publish TechStacks/TechStacks.csproj -c Release --no-restore -p:Publi
5251
# 2. Runtime image with .NET + Node
5352
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
5453
ARG SERVICESTACK_LICENSE
55-
ARG SERVICE_LABEL
54+
ARG SERVICE
5655
ARG KAMAL_DEPLOY_HOST
5756

5857
WORKDIR /app
5958

6059
# Label required by Kamal, must match config/deploy.yml service
61-
LABEL service="${SERVICE_LABEL}"
60+
LABEL service="${SERVICE}"
6261

6362
# Install Node.js >= 20.9 (Node 24.x LTS) and bash for the entrypoint script
6463
RUN apt-get update \
@@ -68,11 +67,19 @@ RUN apt-get update \
6867
&& apt-get clean \
6968
&& rm -rf /var/lib/apt/lists/*
7069

71-
# Copy published .NET app
72-
COPY --from=dotnet-build /src/TechStacks/bin/Release/net10.0/publish ./api
70+
# Create unprivileged user for Next.js
71+
RUN groupadd -r nextjs && useradd -r -g nextjs -s /bin/bash nextjs
7372

74-
# Copy built Next.js app (including dist, node_modules, public, etc.)
75-
COPY --from=dotnet-build /src/TechStacks.Client ./client
73+
# Copy published .NET app (owned by root, no access for nextjs user)
74+
COPY --from=dotnet-build /src/TechStacks/bin/Release/net10.0/publish ./dotnet
75+
RUN chmod -R 700 ./dotnet && chown -R root:root ./dotnet
76+
77+
# Copy built Next.js app (owned by nextjs user, read-only)
78+
COPY --from=dotnet-build /src/TechStacks.Client ./nextjs
79+
RUN chown -R nextjs:nextjs ./nextjs && chmod -R 500 ./nextjs
80+
81+
# Create /tmp directory accessible to nextjs user
82+
RUN mkdir -p /tmp && chmod 1777 /tmp
7683

7784
ENV ASPNETCORE_URLS=http://0.0.0.0:8080 \
7885
INTERNAL_API_URL=http://127.0.0.1:8080 \

NEXTJS_MIGRATION_PLAN.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,7 +2095,6 @@ const nextConfig: NextConfig = {
20952095
"scripts": {
20962096
"dev": "next dev",
20972097
"build": "next build",
2098-
"build:prod": "next build && cp -r out/* ../TechStacks/wwwroot/",
20992098
"start": "next start",
21002099
"lint": "next lint",
21012100
"type-check": "tsc --noEmit"
@@ -2109,7 +2108,7 @@ const nextConfig: NextConfig = {
21092108
{
21102109
"scripts": {
21112110
"ui:dev": "cd nextjs-app && npm run dev",
2112-
"ui:build": "cd nextjs-app && npm run build:prod",
2111+
"ui:build": "cd nextjs-app && npm run build",
21132112
"dtos": "cd TechStacks/src/shared && x ts && cp dtos.ts ../../nextjs-app/src/shared/dtos.ts",
21142113
"publish": "npm run ui:build && cd TechStacks && dotnet publish -c Release",
21152114
"deploy": "npm run publish && bash deploy.sh"

NuGet.Config

Lines changed: 0 additions & 7 deletions
This file was deleted.

TechStacks.Client/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ npm run dev # Start development server
100100

101101
# Build
102102
npm run build # Build for production
103-
npm run build:prod # Build and output to C# wwwroot
104103

105104
# Type checking
106105
npm run type-check # Run TypeScript compiler check
@@ -147,7 +146,7 @@ The production build uses Next.js static export to generate a fully static websi
147146

148147
```bash
149148
# Build for production (static export to ./dist)
150-
NODE_ENV=production npm run build:prod
149+
NODE_ENV=production npm run build
151150

152151
# Build and copy to C# wwwroot (for deployment)
153152
npm run build

TechStacks.Client/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"generate-data": "node scripts/generate-static-data.mjs",
1111
"build": "next build",
1212
"copy-www": "rm -rf ../TechStacks/wwwroot/ && cp -rf dist ../TechStacks/wwwroot && cp dist/_next/static/chunks/*.css ../TechStacks/wwwroot/css/app.css",
13-
"build:prod": "next build",
1413
"start": "next start",
1514
"lint": "next lint",
1615
"type-check": "tsc --noEmit"

TechStacks/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"ui:dev": "tailwindcss -i ./tailwind.input.css -o ./wwwroot/css/app.css --watch",
77
"ui:build": "tailwindcss -i ./tailwind.input.css -o ./wwwroot/css/app.css --minify",
88
"build": "npm run ui:build",
9+
"secret:prod": "gh secret set APPSETTINGS_JSON < appsettings.Production.json",
910
"migrate": "dotnet run --AppTasks=migrate",
1011
"revert:last": "dotnet run --AppTasks=migrate.revert:last",
1112
"revert:all": "dotnet run --AppTasks=migrate.revert:all",

0 commit comments

Comments
 (0)