Skip to content

Commit 86d3da8

Browse files
50 publish the project on az webapp dynamic app (#53)
* feature: create new pipeline to deploy application to azure. * fix: package manager used. * fix: update the nextjs files and remove old dependencies from static project. * fix: remove condition that is skipping the deploy job. * fix: correct the package path * fix: Updated the artifact upload to include the correct build output (dist) along with other necessary files * fix: pnpm command * fix: app name on pipeline and extend docs. * fix: variables used on the pipeline. * chore: Update readme. * fix: files to start the server on az-web-app. * fix: change the installation of packages to pnpm. * fix: simplify and correct startup workflow for azure. * chore: directory veerification for the pipeline. * fix: verify installation of the packages before running the app. * fix: use npm instead of pnpm, and create new pipeline to deploy the app as a container. * chore: change login method for the pipe to admin one (temporarely)
1 parent 99d2659 commit 86d3da8

File tree

13 files changed

+10587
-4987
lines changed

13 files changed

+10587
-4987
lines changed

.dockerignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
node_modules
2+
npm-debug.log
3+
dist
4+
.next
5+
.git
6+
.gitignore
7+
Dockerfile
8+
docker-compose.yml
9+
*.md
10+
*.log
11+
test/
12+
coverage/
13+
.env
14+
.vscode
15+
.idea
16+
17+
# Ignore OS files
18+
.DS_Store
19+
Thumbs.db
20+
21+
# Ignore build artifacts
22+
build
23+
24+
# Ignore package manager files
25+
pnpm-lock.yaml
26+
yarn.lock
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Build and Deploy to Azure Container App
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
workflow_dispatch:
7+
8+
env:
9+
REGISTRY: ${{ secrets.AZURE_CONTAINER_REGISTRY }}
10+
IMAGE_NAME: onlinecv-app
11+
CONTAINER_APP_NAME: ${{ secrets.AZURE_CONTAINER_APP_NAME }}
12+
RESOURCE_GROUP: ${{ secrets.AZURE_RESOURCE_GROUP }}
13+
14+
jobs:
15+
build-and-push:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Log in to Azure Container Registry
21+
uses: azure/docker-login@v1
22+
with:
23+
login-server: ${{ env.REGISTRY }}
24+
username: ${{ secrets.AZURE_CONTAINER_REGISTRY_USERNAME }}
25+
password: ${{ secrets.AZURE_CONTAINER_REGISTRY_PASSWORD }}
26+
27+
- name: Build and push Docker image
28+
run: |
29+
docker build -t $REGISTRY/$IMAGE_NAME:${{ github.sha }} .
30+
docker push $REGISTRY/$IMAGE_NAME:${{ github.sha }}
31+
32+
deploy:
33+
runs-on: ubuntu-latest
34+
needs: build-and-push
35+
steps:
36+
- name: Azure Login
37+
uses: azure/login@v1
38+
with:
39+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
40+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
41+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
42+
43+
- name: Deploy to Azure Container App
44+
run: |
45+
az containerapp update \
46+
--name $CONTAINER_APP_NAME \
47+
--resource-group $RESOURCE_GROUP \
48+
--image $REGISTRY/$IMAGE_NAME:${{ github.sha }} \
49+
--set-env-vars COSMOS_ENDPOINT=${{ secrets.COSMOS_ENDPOINT }} COSMOS_KEY=${{ secrets.COSMOS_KEY }} COSMOS_DATABASE=${{ secrets.COSMOS_DATABASE }} COSMOS_CONTAINER=${{ secrets.COSMOS_CONTAINER }}

.github/workflows/azure-webapp.yml

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,16 @@
1313
# For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
1414
# For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples
1515

16-
on:
17-
push:
18-
branches: [ "main" ]
19-
pull_request:
20-
types: [closed]
21-
branches: [ "main" ]
22-
workflow_dispatch:
16+
# Workflow is deactivated - uncomment the lines below to reactivate
17+
# on:
18+
# push:
19+
# branches: [ "main", "50-publish-the-project-on-az-webapp-dynamic-app" ]
20+
# workflow_dispatch:
2321

2422
env:
25-
AZURE_WEBAPP_NAME: your-actual-webapp-name # ⚠️ UPDATE THIS
26-
AZURE_WEBAPP_PACKAGE_PATH: './dist'
27-
NODE_VERSION: '20.x'
23+
AZURE_WEBAPP_NAME: ${{ vars.AZURE_WEBAPP_NAME }}
24+
AZURE_WEBAPP_PACKAGE_PATH: '.'
25+
NODE_VERSION: ${{ vars.NODE_VERSION }}
2826

2927
permissions:
3028
id-token: write
@@ -36,15 +34,17 @@ jobs:
3634
steps:
3735
- uses: actions/checkout@v4
3836

37+
- name: Install pnpm
38+
uses: pnpm/action-setup@v4
39+
with:
40+
version: 8
41+
3942
- name: Set up Node.js
4043
uses: actions/setup-node@v4
4144
with:
4245
node-version: ${{ env.NODE_VERSION }}
4346
cache: 'pnpm'
4447

45-
- name: Install pnpm
46-
run: npm install -g pnpm
47-
4848
- name: Install dependencies and build
4949
run: |
5050
pnpm install
@@ -55,11 +55,31 @@ jobs:
5555
COSMOS_DATABASE: ${{ secrets.COSMOS_DATABASE }}
5656
COSMOS_CONTAINER: ${{ secrets.COSMOS_CONTAINER }}
5757

58+
- name: Create deployment package
59+
run: |
60+
# Create a deployment directory
61+
mkdir -p deployment
62+
# Copy essential files for Azure Web App
63+
cp package.json deployment/
64+
cp next.config.js deployment/
65+
cp pnpm-lock.yaml deployment/
66+
cp server.js deployment/
67+
# Copy build output (check for both .next and dist directories)
68+
if [ -d ".next" ]; then cp -r .next deployment/; fi
69+
if [ -d "dist" ]; then cp -r dist deployment/; fi
70+
cp -r public deployment/
71+
cp -r app deployment/
72+
# Create a startup script that installs dependencies and starts the app
73+
echo '#!/bin/bash' > deployment/startup.sh
74+
echo 'npm install --production' >> deployment/startup.sh
75+
echo 'npm start' >> deployment/startup.sh
76+
chmod +x deployment/startup.sh
77+
5878
- name: Upload artifact for deployment job
5979
uses: actions/upload-artifact@v4
6080
with:
6181
name: node-app
62-
path: ./dist
82+
path: deployment
6383

6484
deploy:
6585
permissions:
@@ -70,7 +90,7 @@ jobs:
7090
environment:
7191
name: 'production'
7292
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
73-
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
93+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
7494

7595
steps:
7696
- name: Download artifact from build job

.github/workflows/readme.md

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ az ad sp create --id <APP_ID>
4646

4747
## Step 3: Create Federated Credentials
4848

49+
Check if the application has already some federated-credentials created:
50+
```bash
51+
az ad app federated-credential list --id <APP_ID>
52+
```
53+
4954
Replace the following values with your actual information:
5055

5156
- `<APP_ID>`: Application ID from Step 1
@@ -55,14 +60,28 @@ Replace the following values with your actual information:
5560
az ad app federated-credential create \
5661
--id <APP_ID> \
5762
--parameters '{
58-
"name": "GitHub-Actions-Main",
63+
"name": "GitHub-Actions-OnlineCV",
5964
"issuer": "https://token.actions.githubusercontent.com",
60-
"subject": "repo:your-github-username/onlineCv:ref:refs/heads/main",
65+
"subject": "repo:franciscosuca/onlineCv:environment:production",
6166
"description": "GitHub Actions deployment from main branch",
6267
"audiences": ["api://AzureADTokenExchange"]
6368
}'
6469
```
6570

71+
## Verifying Federated Credentials in Azure Portal
72+
73+
To verify your federated credentials were created successfully:
74+
75+
1. **Navigate to Azure Portal** - Go to [portal.azure.com](https://portal.azure.com)
76+
2. **Access Microsoft Entra ID** - Navigate to Microsoft Entra ID (formerly Azure Active Directory)
77+
3. **Find App Registrations** - Go to **App registrations**
78+
4. **Locate Your App** - Find your app "GitHub-Actions-OnlineCV" (App ID: `<APP_ID>)
79+
5. **View Credentials** - Click on **Certificates & secrets**
80+
6. **Check Federated Credentials** - Click on the **Federated credentials** tab
81+
82+
You should see your "GitHub-Actions-OnlineCV" federated credential listed here.
83+
84+
6685
## Step 4: Assign Azure Permissions
6786

6887
### Option A: Resource Group Level Permissions (Recommended)
@@ -110,6 +129,20 @@ In your GitHub repository:
110129
| `AZURE_TENANT_ID` | Tenant ID from Step 1 | Azure tenant identifier |
111130
| `AZURE_SUBSCRIPTION_ID` | Subscription ID from Step 1 | Azure subscription identifier |
112131

113-
## Step 6: Update Workflow Configuration
114-
115-
Update the `AZURE_WEBAPP_NAME` environment variable in your GitHub Actions workflow file with your actual Azure Web App name.
132+
## Step 6: Create app-service-plan and web-application in Azure (if not created yet)
133+
134+
````bash
135+
az appservice plan create \
136+
--name <Plan_name> \
137+
--resource-group <yourResourceGroup> \
138+
--sku B1 \
139+
--is-linux
140+
````
141+
142+
````bash
143+
az webapp create \
144+
--name <APP_NAME> \
145+
--resource-group <yourResourceGroup> \
146+
--plan <your_Plan> \
147+
--runtime "NODE:20-lts"
148+
````

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Use official Node.js LTS image
2+
FROM node:20-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy package files and install dependencies
7+
COPY package.json package-lock.json ./
8+
RUN npm install --legacy-peer-deps
9+
10+
# Copy the rest of the app source
11+
COPY . .
12+
13+
# Build the Next.js app
14+
RUN npm run build
15+
16+
# Production image
17+
FROM node:20-alpine AS runner
18+
19+
WORKDIR /app
20+
21+
# Copy only necessary files from builder
22+
COPY --from=builder /app/package.json ./
23+
COPY --from=builder /app/package-lock.json ./
24+
COPY --from=builder /app/dist ./dist
25+
COPY --from=builder /app/public ./public
26+
COPY --from=builder /app/app ./app
27+
COPY --from=builder /app/next.config.js ./
28+
COPY --from=builder /app/server.js ./
29+
30+
RUN npm ci --only=production --legacy-peer-deps
31+
32+
# Expose port (change if your app uses a different port)
33+
EXPOSE 8080
34+
35+
# Start the app
36+
CMD ["node", "server.js"]

app/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const baseUrl = "https://portfolio-blog-starter.vercel.app";

app/layout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { Navbar } from './components/nav'
66
import { Analytics } from '@vercel/analytics/react'
77
import { SpeedInsights } from '@vercel/speed-insights/next'
88
import Footer from './components/footer'
9-
10-
export const baseUrl = "https://portfolio-blog-starter.vercel.app";
9+
import { baseUrl } from './config'
1110

1211
export const metadata: Metadata = {
1312
metadataBase: new URL(baseUrl),

app/utils/cosmosDB.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import { CosmosClient, Database, Container } from '@azure/cosmos';
22

3-
if (!process.env.COSMOS_ENDPOINT || !process.env.COSMOS_KEY) {
4-
throw new Error('Missing required environment variables for Cosmos DB');
5-
}
3+
// Check if we're in build mode or runtime mode
4+
const isBuildTime = process.env.NODE_ENV === undefined || process.env.NEXT_PHASE === 'phase-production-build';
65

7-
const client = new CosmosClient({
8-
endpoint: process.env.COSMOS_ENDPOINT,
9-
key: process.env.COSMOS_KEY,
10-
});
6+
// Initialize client only if not in build time and env vars are available
7+
let client: CosmosClient | null = null;
8+
if (!isBuildTime && process.env.COSMOS_ENDPOINT && process.env.COSMOS_KEY) {
9+
client = new CosmosClient({
10+
endpoint: process.env.COSMOS_ENDPOINT,
11+
key: process.env.COSMOS_KEY,
12+
});
13+
}
1114

1215
const databaseId = process.env.COSMOS_DATABASE || 'onlineCv';
1316
const containerId = process.env.COSMOS_CONTAINER || 'experience';
1417
let databaseInstance: Database | null = null;
1518
let containerInstance: Container | null = null;
1619

1720
async function initializeCosmosDB() {
21+
if (!client) {
22+
throw new Error('Cosmos DB client not initialized. Missing environment variables.');
23+
}
24+
1825
if (!databaseInstance || !containerInstance) {
1926
try {
2027
databaseInstance = client.database(databaseId);
@@ -27,6 +34,10 @@ async function initializeCosmosDB() {
2734
}
2835

2936
export async function getItem<T>(id: string, partitionKey: string): Promise<T | null> {
37+
if (!client) {
38+
return null; // Return null during build time
39+
}
40+
3041
const { container } = await initializeCosmosDB();
3142
try {
3243
const { resource } = await container.item(id, partitionKey).read();
@@ -40,6 +51,10 @@ export async function getItem<T>(id: string, partitionKey: string): Promise<T |
4051
}
4152

4253
export async function queryItems<T>(partitionKey: string): Promise<T[]> {
54+
if (!client) {
55+
return []; // Return empty array during build time
56+
}
57+
4358
const { container } = await initializeCosmosDB();
4459
const { resources } = await container.items
4560
.query("SELECT * FROM c", {

0 commit comments

Comments
 (0)