> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manual Lambda Deployment

> Guide to manually package a Node.js Express app, upload ZIP to S3, and update an existing AWS Lambda function, including handler setup and testing via Function URL.

In this guide you'll manually deploy a Node.js Express application to AWS Lambda (Node.js 20.x). We will package the app, upload the ZIP to S3, and update an existing Lambda function. In a follow-up lesson we'll automate this with [Jenkins Pipelines](https://learn.kodekloud.com/user/courses/jenkins-pipelines).

Below are the AWS objects created for this deployment and their purposes.

|   Resource Type | Name / Identifier          | Purpose                                   |
| --------------: | -------------------------- | ----------------------------------------- |
|       S3 Bucket | solar-system-lambda-bucket | Store the deployment ZIP for Lambda       |
| Lambda Function | solar-system-function      | The existing function we will update      |
|    Function URL | (Lambda Function URL)      | Public endpoint to verify UI and behavior |

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/s3-console-solar-system-buckets.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=da0aba7d4eb47a3cd5dcb7d055c1d441" alt="A screenshot of the AWS S3 console showing the &#x22;General purpose buckets&#x22; list with two buckets: &#x22;solar-system-jenkins-reports-bucket&#x22; and &#x22;solar-system-lambda-bucket&#x22; in the US East (Ohio) us-east-2 region. The page shows creation dates, IAM Access Analyzer links, and controls like &#x22;Create bucket.&#x22;" width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/s3-console-solar-system-buckets.jpg" />
</Frame>

We will create a ZIP archive of the minimal application artifacts, upload it to the S3 bucket above, and use `aws lambda update-function-code` to point the Lambda function to that S3 object. The Lambda function already exists; we will update it instead of creating a new function.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-nodejs20x-code-9-9mb.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=628e557a12634ac58bc1c1d92f12b09a" alt="A screenshot of the AWS Lambda console showing a function's Code properties and Runtime settings, including package size (9.9 MB), SHA256 hash, and last modified timestamp. It lists the runtime as Node.js 20.x, handler app.handler, and architecture x86_64." width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-nodejs20x-code-9-9mb.jpg" />
</Frame>

Key Lambda details:

* Runtime: Node.js 20.x
* Handler: app.handler (project uses app.js)
* Current package size: \~10 MB

The packaged application contains server-side JavaScript plus static assets (HTML, CSS, images). Example static HTML snippet found in the repo:

```html theme={null}
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Solar System - Sidd</title>
    <link rel="icon" type="image/x-icon" href="https://gitlab.com/sidd-larth/solar-system/-/raw/main/images/favicon.ico">
    <style>
      #planetImage {
        background: url('https://gitlab.com/sidd-larth/solar-system/-/raw/main/images/solar-system.png');
        background-repeat: no-repeat;
        background-size: cover;
        position: static;
        animation: spin 20s linear infinite;
        width: 30vw;
      }
    </style>
  </head>
  <body>
  </body>
</html>
```

Important: the deployed Lambda currently has MongoDB credentials stored as plaintext environment variables.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-env-vars-solar-function.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=ef324bd5936092a7e260e68ad43c7652" alt="A screenshot of the AWS Lambda console for a function named &#x22;solar-system-function,&#x22; open to the Configuration → Environment variables tab showing keys like MONGO_PASSWORD, MONGO_URI, and MONGO_USERNAME with their values. The page also shows navigation tabs, action buttons (Throttle, Copy ARN, Actions), and the left-side configuration menu." width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-env-vars-solar-function.jpg" />
</Frame>

<Callout icon="warning" color="#FF6B6B">
  Do not store sensitive secrets in plain environment variables for production. Use a secrets manager and grant the Lambda execution role least privilege.
</Callout>

<Callout icon="lightbulb" color="#1CB2FE">
  For production deployments, avoid hardcoding sensitive values. Use [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) or [AWS Systems Manager Parameter Store (SSM)](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) and grant the Lambda role least privilege to retrieve secrets.
</Callout>

The Lambda function is exposed with a Function URL (you can view and test this in the Lambda console).

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=61cc36596e48ef8f9fb295fa35bd9348" alt="A screenshot of the AWS Lambda console on the Configuration > Function URL tab, showing a public function URL with Auth type &#x22;NONE&#x22;, Invoke mode &#x22;BUFFERED&#x22;, and CORS settings. The left sidebar lists other configuration sections like Triggers, Permissions, and Environment variables." data-og-width="1920" width="1920" data-og-height="1080" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=280&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=73e1108aa6d4584bf430238e2549ba1c 280w, https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=560&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=6cf2abd369520cf3140fa47837f366a0 560w, https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=840&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=37a30dc719ab9934da6b39c5d749486b 840w, https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=1100&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=07dda5d0a5c524dd631873f74e849f96 1100w, https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=1650&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=08d1108e4c5a44a449b9875ad743109c 1650w, https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-lambda-function-url-none-buffered.jpg?w=2500&fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=41d07a04d1971cac7e56557ae4c12fb8 2500w" />
</Frame>

Preparing the local workspace and packaging the app

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/dark-repo-dasher-org-solar-system.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=fb9fff39b0b5af668e66b4d8bfefdd37" alt="A dark-themed browser view of a code repository page (dasher-org/solar-system) showing the file list, recent commits, branches and repository stats. The page displays filenames like Dockerfile, app.js and README.md along with commit messages and timestamps." width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/dark-repo-dasher-org-solar-system.jpg" />
</Frame>

Project details and Node.js serverless setup:

* The app uses serverless-http to wrap an Express app for Lambda.
* The handler export should be: module.exports.handler = serverless(app)
* If running locally with app.listen, those lines must be commented/removed for Lambda.

Example package.json dependencies (serverless-http included):

```json theme={null}
{
  "nyc": {
    "check-coverage": true,
    "lines": 90
  },
  "dependencies": {
    "@babel/traverse": "^7.23.2",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "mocha-junit-reporter": "^2.2.1",
    "mongoose": "5.13.20",
    "nyc": "^15.1.0",
    "serverless-http": "^3.2.0"
  },
  "devDependencies": {
    "chai": "*",
    "chai-http": "*",
    "mocha": "*"
  }
}
```

Relevant excerpt from app.js showing serverless-http usage and MongoDB env var consumption:

```javascript theme={null}
const path = require('path');
const fs = require('fs');
const express = require('express');
const os = require('os');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const cors = require('cors');
const serverless = require('serverless-http');

const app = express();

app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, '/')));
app.use(cors());

mongoose.connect(process.env.MONGO_URI, {
  user: process.env.MONGO_USERNAME,
  pass: process.env.MONGO_PASSWORD,
  useNewUrlParser: true,
  useUnifiedTopology: true
}, function(err) {
  if (err) {
    console.error('MongoDB connection error:', err);
  }
});

// If running locally, the app might have these lines:
// app.listen(3000, () => { console.log("Server successfully running on port - " + 3000); })
// module.exports = app;

// For Lambda we must export the serverless handler:
module.exports.handler = serverless(app);
```

Deployment checklist (high-level)

1. Clone the repository and install dependencies.
2. Apply any app changes to verify deployment (e.g., bump a visible version string).
3. Remove or comment local server start code (app.listen) and ensure the Lambda handler export is present.
4. Zip the minimal files required by Lambda (app files, package.json, index.html, node\_modules).
5. Upload the ZIP to S3.
6. Update the Lambda function using the S3 object via AWS CLI.
7. Retrieve and test the Function URL.

Recommended shell commands (run from a workspace/sandbox):

```bash theme={null}
# clone and enter repo (example)
git clone https://gitlab.com/dasher-org/solar-system.git
cd solar-system

# install dependencies (creates node_modules)
npm install

# update index.html version (example using sed)
# (This is just an example change to indicate a new version in the UI)
sed -i 's/SOLAR SYSTEM 3.0/SOLAR SYSTEM 4.0/' index.html

# Modify app.js for Lambda:
# Comment any local app.listen line and module.exports = app;
sed -i 's/^app\.listen(3000.*$/\/\/&/' app.js
sed -i 's/^module\.exports = app;/\/\/&/' app.js

# Ensure the serverless handler line is uncommented:
sed -i 's/^\/\/module\.exports\.handler/module.exports.handler/' app.js

# Verify the last 5 lines to confirm changes
tail -n 5 app.js

# Create the deployment ZIP with the needed files (app*, package*, index.html, node*)
zip -qr solar-system-lambda.zip app* package* index.html node*

# Upload the ZIP to S3
aws s3 cp solar-system-lambda.zip s3://solar-system-lambda-bucket/solar-system-lambda.zip

# Update the Lambda function from the S3 object
aws lambda update-function-code --function-name solar-system-function --s3-bucket solar-system-lambda-bucket --s3-key solar-system-lambda.zip

# Retrieve function URL config to get the FunctionUrl
aws lambda get-function-url-config --function-name solar-system-function
```

After uploading the ZIP, verify the S3 object exists in the console.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-s3-console-solar-system-lambda.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=7d4bf910e997fc1226d73c29fd3f607f" alt="A screenshot of the AWS S3 console showing the bucket &#x22;solar-system-lambda-bucket&#x22; with a single object: &#x22;solar-system-lambda.zip&#x22; (9.9 MB) and its last modified timestamp. The S3 toolbar (Upload, Actions, Copy URL, etc.) and bucket tabs (Objects, Properties, Permissions) are also visible." width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/aws-s3-console-solar-system-lambda.jpg" />
</Frame>

When the CLI update completes, it returns function metadata (JSON) including LastModified, CodeSize, and the environment variables currently configured. Example (truncated):

```json theme={null}
{
  "FunctionName": "solar-system-function",
  "Runtime": "nodejs20.x",
  "Handler": "app.handler",
  "CodeSize": 10332446,
  "LastModified": "2024-10-02T07:36:54.000+0000",
  "Version": "$LATEST",
  "Environment": {
    "Variables": {
      "MONGO_USERNAME": "superuser",
      "MONGO_PASSWORD": "SuperPassword",
      "MONGO_URI": "mongodb+srv://supercluster.d83jj.mongodb.net/superData"
    }
  }
}
```

To obtain the Function URL programmatically:

```bash theme={null}
aws lambda get-function-url-config --function-name solar-system-function
```

Example response (truncated):

```json theme={null}
{
  "FunctionUrl": "https://jkg12znysg2qhkphl6766t2p5de0jocjm.lambda-url.us-east-2.on.aws/",
  "AuthType": "NONE",
  "Cors": {
    "AllowOrigins": ["*"]
  },
  "InvokeMode": "BUFFERED"
}
```

Open that Function URL (or refresh in the browser) to verify the deployment and any UI changes you made (the example uses "SOLAR SYSTEM 4.0").

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/qTPiNmqXKGQjbUh5/images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/solar-system-4-0-search-panel.jpg?fit=max&auto=format&n=qTPiNmqXKGQjbUh5&q=85&s=973b26fd63f6e6c0bc299f80a2219849" alt="A web page titled &#x22;SOLAR SYSTEM 4.0&#x22; with a starry background showing a colorful, cartoon-style Sun and planets arranged in orbital rings on the right. On the left are a purple search/input panel and descriptive text about the solar system." width="1920" height="1080" data-path="images/Jenkins-Pipelines/AWS-Lambda-and-Advanced-Deployment-Techniques/Manual-Lambda-Deployment/solar-system-4-0-search-panel.jpg" />
</Frame>

Concise summary of the manual deployment steps:

* Ensure app exports a Lambda-compatible handler (module.exports.handler = serverless(app)) and remove local app.listen lines.
* Install dependencies with npm install.
* Create a ZIP containing only the files Lambda needs.
* Upload the ZIP to an S3 bucket.
* Update the Lambda function with aws lambda update-function-code referencing the S3 bucket and key.
* Confirm deployment by retrieving the Function URL and testing the endpoint.

Links and references

* [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html)
* [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)
* [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
* [AWS CLI Lambda Commands](https://docs.aws.amazon.com/cli/latest/reference/lambda/index.html)
* [serverless-http on npm](https://www.npmjs.com/package/serverless-http)

Next lesson: Automate these steps and make deployments repeatable and secure with [Jenkins Pipelines](https://learn.kodekloud.com/user/courses/jenkins-pipelines).

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/jenkins-pipelines/module/78297356-097c-4793-b690-bc83f9aba3f0/lesson/4be4b517-e6be-4d8a-bf33-2a305eb9995b" />
</CardGroup>
