Skip to content

Commit 9ad9d5c

Browse files
testing vps deployment
1 parent 48917e0 commit 9ad9d5c

53 files changed

Lines changed: 3642 additions & 37414 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# Serverpod Deployment to a VPS using Docker
2+
3+
> 💡 Deploying Serverpod to a Virtual Private Server (VPS) using Docker is a
4+
> cost-effective and scalable solution for startups and small- to medium-scale
5+
> projects.
6+
7+
This guide walks you through deploying a server built using the Serverpod
8+
framework to a Virtual Private Server (VPS) with Docker.
9+
10+
Serverpod is a Flutter/Dart backend framework offering database integration and
11+
seamless client–server communication. VPS deployment provides a cost-effective
12+
solution for small to medium projects.
13+
14+
The setup allows vertical scaling through VM upgrades and can be extended to
15+
horizontal scaling with load balancers. This guide helps you create a
16+
production-ready Docker Compose deployment.
17+
18+
To reduce the workload on the machine, we do not use Redis in this deployment.
19+
(Redis becomes necessary when you want to scale your application horizontally.)
20+
21+
> 💡 In many cases, scaling vertically is sufficient and saves you the hassle of
22+
> setting up a load balancer and additional infrastructure. Always start with
23+
> vertical scaling and only scale horizontally if you need to.
24+
25+
## Prerequisites
26+
27+
- This guide assumes you have basic knowledge of Serverpod and command-line usage.
28+
- This guide contains terminal commands that are specific to Unix-based systems (macOS & Linux).
29+
- The `docker-compose.production.yaml` file is configured to run on ARM machines.
30+
31+
## Table of Contents
32+
33+
- [Prerequisites](#prerequisites)
34+
- [Table of Contents](#table-of-contents)
35+
- [Preparing the server](#preparing-the-server)
36+
- [Registering at Hetzner Cloud](#registering-at-hetzner-cloud)
37+
- [Setting up an SSH key to connect to the server](#setting-up-an-ssh-key-to-connect-to-the-server)
38+
- [Creating a new server](#creating-a-new-server)
39+
- [Setting up the server](#setting-up-the-server)
40+
- [Step 1: Create the new user](#step-1-create-the-new-user)
41+
- [Step 2: Grant Docker permissions](#step-2-grant-docker-permissions)
42+
- [Step 3: Enable SSH access](#step-3-enable-ssh-access)
43+
- [Step 4: Set up SSH key-based authentication](#step-4-set-up-ssh-key-based-authentication)
44+
- [Firewall configuration](#firewall-configuration)
45+
- [Preparing the domain](#preparing-the-domain)
46+
- [Preparing the repository](#preparing-the-repository)
47+
- [Getting a GitHub Personal Access Token](#getting-a-github-personal-access-token)
48+
- [Adding the secrets to the repository](#adding-the-secrets-to-the-repository)
49+
- [Creating the deployment files](#creating-the-deployment-files)
50+
- [Configuring SSL-certificates](#configuring-ssl-certificates)
51+
- [Configuring the GitHub-Action](#configuring-the-github-action)
52+
- [Running the GitHub-Action](#running-the-github-action)
53+
- [Using the Serverpod Insights app](#using-the-serverpod-insights-app)
54+
- [Connecting your Flutter client](#connecting-your-flutter-client)
55+
- [Connecting to the Database using DBeaver](#connecting-to-the-database-using-dbeaver)
56+
57+
## Preparing the server
58+
59+
This guide uses Hetzner Cloud. You can use any server provider, but Hetzner is a
60+
good and cost-effective option. If you want to use another architecture or
61+
provider, check the Docker Compose file and the deployment script for any
62+
necessary changes. Currently, the deployment is meant to run on ARM machines.
63+
64+
### Registering at Hetzner Cloud
65+
66+
Register an account at Hetzner Cloud and create a new project.
67+
[Use this referral link to get €20 credits for free at Hetzner Cloud](https://hetzner.cloud/?ref=BFdFFipLgfDs)
68+
69+
Next, go to the [Cloud Console](https://console.hetzner.cloud/) and create a project.
70+
71+
### Setting up an SSH key to connect to the server
72+
73+
In order to configure your server, you need to access it through SSH. Create an
74+
SSH keypair if you don't have one yet. If you are not sure whether you already
75+
have one, you can check by running:
76+
77+
```bash
78+
cat ~/.ssh/id_rsa.pub
79+
```
80+
81+
To create a new keypair, run the following command. Leave all options at their
82+
default values by pressing enter.
83+
84+
```bash
85+
ssh-keygen -t rsa -b 4096
86+
```
87+
88+
When asked for a password, don't enter anything—just press enter. This will
89+
create a keypair in `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub`.
90+
91+
Copy the public key to your clipboard:
92+
93+
```bash
94+
cat ~/.ssh/id_rsa.pub
95+
```
96+
97+
Select the output and copy it.
98+
99+
In your Hetzner project, follow these steps:
100+
101+
1. In the left-hand menu, click on **Security** > **SSH keys** > **Add SSH key**.
102+
2. Paste the public key you generated earlier.
103+
104+
### Creating a new server
105+
106+
Continuing in your Hetzner project, create a new server:
107+
108+
1. In the left-hand menu, go to **Server** and click **Create server**.
109+
2. In the **Image** section, click on **Apps** and select **Docker CE**.
110+
3. **Type/Architecture:** Select **vCPU** and **Arm64 (Ampere)**. The smallest tier is sufficient for most projects—you can always upgrade the specs later.
111+
4. Ensure that the public IPv4 address is enabled.
112+
5. In the SSH-Keys section, make sure your SSH key is selected.
113+
6. Name your server and create it.
114+
115+
### Setting up the server
116+
117+
Once the server is created, you can connect to it using SSH. Find the server IP
118+
in the Hetzner Cloud Console and connect using the following command:
119+
120+
```bash
121+
ssh root@<your-server-ip>
122+
```
123+
124+
When prompted with "Are you sure you want to continue connecting? [...]" type "yes" and press enter.
125+
126+
> If you are asked for a password, it means the SSH key was not added correctly.
127+
> Delete the corresponding entry from `~/.ssh/known_hosts` and delete the
128+
> server. Then create a new server and ensure the SSH key is added correctly.
129+
130+
For security reasons, we will create a new user to manage the deployment. This user will not have root privileges.
131+
132+
#### Step 1: Create the new user
133+
134+
```bash
135+
sudo adduser github-actions
136+
```
137+
138+
Replace `github-actions` with your desired username. This command will prompt
139+
you to set a password and enter user information.
140+
141+
#### Step 2: Grant Docker permissions
142+
143+
Add the user to the `docker` group so they can run Docker commands:
144+
145+
```bash
146+
sudo usermod -aG docker github-actions
147+
```
148+
149+
#### Step 3: Enable SSH access
150+
151+
SSH access is available by default for any user on the server. However, to ensure proper access, check the `sshd_config` file:
152+
153+
```bash
154+
sudo nano /etc/ssh/sshd_config
155+
```
156+
157+
Find or add the `AllowUsers` directive. This directive specifies which users are
158+
allowed to SSH into the server. If it doesn't exist, add it at the end of the
159+
file. If there are multiple users, separate them with spaces:
160+
161+
```text
162+
AllowUsers root github-actions
163+
```
164+
165+
To save and exit the file, press `Ctrl + X`, then `Y`, and finally `Enter`. Restart the SSH service to apply the changes:
166+
167+
```bash
168+
sudo systemctl restart ssh
169+
```
170+
171+
#### Step 4: Set up SSH key-based authentication
172+
173+
1. Log in as the new user:
174+
175+
```bash
176+
su - github-actions
177+
```
178+
179+
2. Create an SSH keypair:
180+
181+
```bash
182+
ssh-keygen -t rsa -b 4096
183+
```
184+
185+
Leave the options at their default values by pressing enter.
186+
187+
3. Add the public SSH key to the `authorized_keys` file:
188+
189+
```bash
190+
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
191+
```
192+
193+
4. Copy the private key to your clipboard—including the lines `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`. Save this key in a secure location, as you will need it later. To display the private key, run:
194+
195+
```bash
196+
cat ~/.ssh/id_rsa
197+
```
198+
199+
5. Logout from the github-actions user:
200+
201+
```bash
202+
exit
203+
```
204+
205+
6. Restart the SSH service to apply changes:
206+
207+
```bash
208+
sudo systemctl restart ssh
209+
```
210+
211+
### Firewall configuration
212+
213+
In the Hetzner Web Interface, enter your server configuration and click on **Firewalls**, then click **Create Firewall**.
214+
215+
By default, there will be two inbound rules: one for SSH (port 22) and one for ICMP. We will add two more for HTTP and HTTPS.
216+
217+
1. Click on **Add Rule**, name it HTTP, set the port to 80, and choose TCP as the protocol.
218+
2. Click on **Add Rule**, name it HTTPS, set the port to 443, and choose TCP as the protocol.
219+
3. In the "apply to" section, make sure your server is selected.
220+
4. Click **Create Firewall**.
221+
222+
## Preparing the domain
223+
224+
To access your server, you need to have a domain. You can purchase a domain from
225+
any provider (e.g., [Namecheap](https://www.namecheap.com/) or
226+
[GoDaddy](https://www.godaddy.com/)).
227+
228+
Once you have a domain, set up the DNS records to point to your server. Create
229+
the following DNS records, replacing `Your server IP` with the IP address of
230+
your server:
231+
232+
| Type | Name | Value |
233+
| ---- | -------- | -------------- |
234+
| A | api | Your server IP |
235+
| A | web | Your server IP |
236+
| A | insights | Your server IP |
237+
238+
The full domains will be `api.your-domain.com`, `web.your-domain.com`, and `insights.your-domain.com`.
239+
240+
## Preparing the repository
241+
242+
### Getting a GitHub Personal Access Token
243+
244+
1. Create a new Personal Access Token (PAT) on GitHub:
245+
- Click on your profile picture in the top-right corner.
246+
- Navigate to **Settings** > **Developer settings** > **Personal access tokens** > **Tokens (classic)**.
247+
- Click on **Generate new token**.
248+
249+
2. Fill in the required fields:
250+
- In the **Note** field, set a name for the token, e.g., "Serverpod Deployment".
251+
- Set the expiration to **No expiration**.
252+
253+
3. Select the necessary scopes:
254+
- **repo**: Required to read repositories, especially private ones.
255+
- **write:packages**: Required to push Docker images to the GitHub Package Registry.
256+
257+
4. Generate and save the token:
258+
- Scroll to the bottom and click **Generate token**.
259+
- Copy the token and save it in a secure place.
260+
261+
### Adding the secrets to the repository
262+
263+
Go to your Serverpod project repository, then navigate to **Settings** > **Secrets and variables** > **Actions** and create the following secrets:
264+
265+
| Secret Name | Value |
266+
| --------------- | --------------------------------------------------------------- |
267+
| PAT_USER_GITHUB | Your GitHub username |
268+
| PAT_GITHUB | Your GitHub PAT token |
269+
| SSH_HOST | The IP address of your server |
270+
| SSH_USER | The username you created on the server (e.g., "github-actions") |
271+
| SSH_PRIVATE_KEY | The private key you generated on the server |
272+
273+
The following secrets configure Serverpod and the database:
274+
275+
| Secret Name | Value |
276+
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
277+
| SERVERPOD_DATABASE_NAME | The name of the database (e.g., "serverpod") |
278+
| SERVERPOD_DATABASE_USER | The database user (e.g., "serverpod") |
279+
| SERVERPOD_DATABASE_PASSWORD | The database password |
280+
| SERVERPOD_API_SERVER_PUBLIC_HOST | The domain for the API server (e.g., api.my-domain.com) |
281+
| SERVERPOD_WEB_SERVER_PUBLIC_HOST | The domain for the Web server (e.g., web.my-domain.com) |
282+
| SERVERPOD_INSIGHTS_SERVER_PUBLIC_HOST | The domain for the Insights server (e.g., insights.my-domain.com) |
283+
| SERVERPOD_SERVICE_SECRET | The same value as in your local `passwords.yaml` file, required to connect using the Serverpod Insights app |
284+
285+
## Creating the deployment files
286+
287+
The CLI will generate all necessary deployment files for your project.
288+
289+
1. Install the Serverpod VPS CLI:
290+
291+
```bash
292+
dart pub global activate serverpod_vps
293+
```
294+
295+
2. Navigate to your Serverpod project directory:
296+
297+
```bash
298+
cd my_serverpod_project
299+
```
300+
301+
3. Run the CLI to generate deployment files:
302+
303+
```bash
304+
serverpod_vps
305+
```
306+
307+
4. When prompted, enter your email address for SSL certificate notifications.
308+
309+
## Configuring SSL-certificates
310+
311+
All external connections are secured by Traefik through HTTPS. Traefik uses
312+
[Let's Encrypt](https://letsencrypt.org/) to automatically generate SSL
313+
certificates for your domains.
314+
315+
If you need to change the email address that Let's Encrypt uses for certificate
316+
notifications, edit the email address in the `docker-compose.production.yaml`
317+
file. Open the file and modify the value of the parameter
318+
`certificatesresolvers.myresolver.acme.email`.
319+
320+
## Configuring the GitHub-Action
321+
322+
From the root of your repository, open the `.github/workflows/deployment-docker.yml` file and adjust the following settings:
323+
324+
- Update the `GHCR_ORG` variable by replacing `<ORGANIZATION>` with your GitHub username or organization name.
325+
- At the top of the file, you can change the branches that automatically trigger the deployment. By default, it is set to `main`. You can also trigger the action manually on a different branch.
326+
327+
## Running the GitHub-Action
328+
329+
Push your changes to the repository on the configured branch.
330+
331+
To manually trigger the action, go to the **Actions** tab in your repository,
332+
click on the **Deploy to Docker** workflow, then click **Run workflow** and
333+
select the branch you want to deploy.
334+
335+
## Using the Serverpod Insights app
336+
337+
To enable the [Serverpod Insights app](https://docs.serverpod.dev/tools/insights),
338+
adjust the insights server host in `production.yaml` to the domain you set up in
339+
your DNS records. Ensure that the service secret specified in the repository
340+
secrets matches the one in your local `passwords.yaml` file for production.
341+
342+
## Connecting your Flutter client
343+
344+
To connect with your generated client, use the domain you set up in the DNS.
345+
Make sure to use HTTPS without any port numbers (e.g.,
346+
`https://api.my-domain.com`).
347+
348+
## Connecting to the Database using DBeaver
349+
350+
To manage your database, you can use a tool like [DBeaver](https://dbeaver.io/).
351+
To connect to the database, set up an SSH tunnel to the server using the
352+
following example:
353+
354+
1. Open DBeaver and click the **New Database Connection** button (usually in the top-left corner).
355+
2. Select **PostgreSQL** and click **Next**.
356+
3. Click the **SSH** tab.
357+
4. Check **Use SSH tunnel**.
358+
5. Set the following values:
359+
- **Host/IP:** Your server IP
360+
- **Port:** 22
361+
- **Username:** root
362+
- **Authentication method:** Public key
363+
- **Private key:** The private key on your machine you [generated earlier](#setting-up-an-ssh-key-to-connect-to-the-server)
364+
6. Click **Test tunnel** to verify the connection.
365+
7. Return to the **Main** tab.
366+
8. Set the following values:
367+
- **Host:** localhost
368+
- **Port:** 5432
369+
- **Database:** The database name you set in the [repository secrets](#adding-the-secrets-to-the-repository)
370+
- **User name:** The database user you set in the [repository secrets](#adding-the-secrets-to-the-repository)
371+
- **Password:** The database password you set in the [repository secrets](#adding-the-secrets-to-the-repository)
372+
9. Test the connection and save it.

0 commit comments

Comments
 (0)