Slink
Overview
Slink is an open source, self-hosted image sharing and upload server. It is a private, drop-in replacement for public image hosts. Users upload images through a clean browser interface, each image gets a shareable link, and you keep the files and metadata on infrastructure you control.
Slink pairs a Symfony REST API with a modern SvelteKit browser client, and stores images and data locally with SQLite by default. This guide covers deploying and configuring Slink on Ubuntu 24.04 on Azure, using the SecureAnyCloud Azure Marketplace image.
The image ships the free and open source, AGPL-3.0 licensed Slink server, run the officially supported way as the upstream container pinned by image digest. The container is captured into the VM, so your instance starts in seconds.
Nothing in the image ships with a known secret. Slink generates a unique application secret and JWT signing keypair for each VM on first boot, and this image seeds a per-instance admin account with a random password before the service is reachable. New account sign-ups require administrator approval and anonymous uploads are disabled, so the appliance is private by default.
Slink is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by or sponsored by the Slink project. It ships the free and open source AGPL-3.0 licensed software, unmodified.
What you get:
- Slink server v1.12.3 (the AGPL-3.0 licensed Symfony API and SvelteKit web client), pinned by image digest
- A SQLite application database, created empty on first boot and stored on a persistent volume
- Docker Engine with the application published to the loopback interface only, fronted by nginx on port
80 slink.service,slink-firstboot.service,slink-postboot.serviceandnginx.serviceas systemd units, enabled and active on boot- A unique application secret and JWT signing keypair created by the app per VM on first boot, plus a per-instance admin account with a random password seeded automatically
- Secure defaults: new account sign-ups require administrator approval, anonymous guest uploads are disabled, and EXIF metadata is stripped on upload
- No default login, no shipped secret and an empty database on first boot
- Ubuntu 24.04 LTS base with latest security patches applied at build time
- Azure Linux Agent for cloud integration and SSH key injection
- 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet plus subnet in the target region
- A subscription to the Slink listing on Azure Marketplace
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for a team or an individual. For heavier upload volumes or many concurrent users, use Standard_D2s_v5 or larger.
In the network security group (NSG), allow inbound 22/tcp from your management network and 80/tcp for the web client from the networks that use it.
Deploy from the Azure Portal
Sign in to the Azure portal, choose Create a resource, search the Marketplace for Slink by SecureAnyCloud, and select Create.
- On Basics, pick your subscription, resource group, region and size.
- Under Administrator account, choose SSH public key and paste your key.
- Under Inbound port rules, allow SSH (22) and HTTP (80).
- Choose Review + create, then Create.
Deploy from the Azure CLI
RG="slink-prod"; LOCATION="eastus"; VM_NAME="slink-01" IMAGE_URN="sac1647283583153:slink-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name slink-vnet --address-prefix 10.100.0.0/16 --subnet-name slink-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name slink-nsg az network nsg rule create -g "$RG" --nsg-name slink-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name slink-nsg --name allow-http --priority 110 \
--destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:slink-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name slink-vnet --subnet slink-subnet --nsg slink-nsg --public-ip-sku StandardConnect to your VM
ssh azureuser@<vm-ip>Confirm the services are running
Slink runs as one container under slink.service, fronted by nginx. nginx listens on :80, and the application is published to 127.0.0.1 only. Confirm that the services are active and check the container:
sudo systemctl is-active docker slink-firstboot slink slink-postboot nginx
sudo docker psThe services report active and the container reports running (healthy).
The application (127.0.0.1:3000) is bound only to the loopback interface. nginx on :80 is the single public front door, and the SvelteKit client proxies its API calls to the PHP backend inside the container.
Read the per-instance credentials
On first boot, Slink generates a unique application secret and JWT signing keypair for this VM. This image also seeds a per-instance admin account with a random password before the app is reachable, and writes the details to a root-only file. Read the file:
sudo cat /root/slink-credentials.txtThe file (mode 0600 root:root) holds ADMIN_EMAIL and ADMIN_USERNAME (either can be used to sign in), ADMIN_PASSWORD (the random per-instance password) and WEB_URL.
None of these values ship in the image. Every value is unique to this VM, so no two instances share a credential and there is no default login to change.
Verify the security model
The web client is reachable without authentication, but every protected API endpoint requires a bearer token. Run the following commands to confirm three things: the web client loads, an unauthenticated request to a protected endpoint is rejected with 401, and the per-instance admin credential authenticates a real call:
ADMIN_EMAIL=$(sudo grep '^ADMIN_EMAIL=' /root/slink-credentials.txt | cut -d= -f2-)
ADMIN_PASSWORD=$(sudo grep '^ADMIN_PASSWORD=' /root/slink-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'web client (no auth): %{http_code}\n' -L http://localhost/ curl -s -o /dev/null -w 'user api (no token): %{http_code}\n' -H 'Accept: application/json' http://localhost/api/user
TOKEN=$(curl -s -X POST http://localhost/api/auth/login \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d "{\"username\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -o /dev/null -w 'user api (with token): %{http_code}\n' \
-H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' http://localhost/api/userThe web client returns 200. The protected user endpoint returns 401 when you send no token, and 200 when you send the per-instance admin bearer token. The API is gated by a JWT that you obtain by signing in with the per-instance admin credential.
The same round trip is bundled as a single script on the image. It proves that the credential works end to end:
sudo /usr/local/sbin/slink-roundtrip.shOpen the web client and sign in
Browse to http://<vm-ip>/ to reach the Slink web client. Select Sign In and enter the ADMIN_EMAIL (or ADMIN_USERNAME) and ADMIN_PASSWORD from Step 5. New users can request an account with Sign Up. An administrator must approve each request before the account can be used (see Step 10).
Upload an image
Select Upload to open the drop zone. Drag an image onto it, or click to browse from your device.
Slink accepts JPEG, PNG, GIF, WebP, AVIF, SVG and more, up to the configured maximum size. Uploads default to Private. Before you upload, you can attach tags or add the image to a collection.
View your image and copy its share link
When the upload completes, Slink opens the image details page. It shows your image with its dimensions, format and size, and a ready-to-use share link that you can copy.
From this page you can resize the shared version, convert the format (Original, JPG, WebP or AVIF), download the file, add a description, and set an expiry or a password on the share.
Administer users and settings
As the admin, you have an Administration area with a dashboard, user management and settings.
Open Settings to configure image uploads and sharing, storage, and the security policies (user registration, access control and authentication). Open Users to approve pending sign-ups and manage accounts.
Manage registration and approval
By default, users can request accounts with Sign Up, but an administrator must approve every request before the account becomes active. Anonymous guest uploads are disabled.
Approve or reject pending users under Administration > Users. Change the registration, approval and guest upload policies under Administration > Settings > Security Settings.
These policies are also set as environment defaults in /etc/slink/compose.yaml. If you change them there, apply the change with:
sudo systemctl restart slinkCall the API from your own machine
Every feature of the web client is backed by the REST API, which you can call directly. Sign in to obtain a token, then call a protected endpoint at the VM address:
TOKEN=$(curl -s -X POST http://<vm-ip>/api/auth/login \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"username":"<ADMIN_EMAIL>","password":"<ADMIN_PASSWORD>"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' "http://<vm-ip>/api/user"The login returns a JWT bearer token whose payload identifies the admin (ROLE_ADMIN). Pass the token in an Authorization: Bearer header to reach protected endpoints.
Server components
Component Version / Detail
Application Slink v1.12.3 (Symfony API + SvelteKit client, pinned by image digest)
Front door nginx on :80 proxying to the app on 127.0.0.1:3000
Application database SQLite (created empty on first boot, on a persistent volume)
Image storage local filesystem on a persistent volume (EXIF stripped on upload)
Orchestration Docker Compose under slink.service
Application secret + keys per instance, created by the app on first boot
Admin account per instance random password, seeded first boot into /root/slink-credentials.txt
Operating system Ubuntu 24.04 LTS (patched at build)
License AGPL-3.0 (Slink)
Managing the service
sudo systemctl status slink --no-pager | head -12 sudo docker compose -f /etc/slink/compose.yaml logs --tail 40 slinkRestart the stack with sudo systemctl restart slink. Follow the logs live with sudo docker compose -f /etc/slink/compose.yaml logs -f slink. View the configuration in /etc/slink/slink.env and /etc/slink/compose.yaml. After you change the environment file, restart the service to apply the change.
Use your own domain and HTTPS (production)
The image serves the web client over plain HTTP on port 80, which suits a load balancer or private network. For production, terminate TLS in front of the VM so that credentials travel encrypted. Choose one of these options:
- Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to nginx on port
80. - Install your own certificate. Add a TLS
serverblock to/etc/nginx/sites-available/slinkthat references your certificate and key, open443/tcpon the NSG, then runsudo systemctl reload nginx. - Point a DNS name you control at the VM public IP. The web client reads its public origin (
ORIGIN) at first boot from the resolved public address, so use a stable public IP. With a custom domain, setORIGINin/etc/slink/slink.envto your domain and restart the service.
Security recommendations
- Restrict the NSG. Allow TCP 80 (and 22 for admin) only from the networks that use the app.
- Terminate TLS in front of the app (Step 15) so the admin password and tokens are encrypted in transit.
- Rotate the seeded admin password. Sign in and change it. Keep registration behind admin approval, which is the default.
- Keep the app private. The container is bound to loopback and fronted by nginx. Keep it that way, and never expose the container port directly.
- Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Support and Licensing
Slink is distributed under the GNU Affero General Public License v3.0 (AGPL-3.0). This SecureAnyCloud image bundles the unmodified official open source release.
SecureAnyCloud provides the packaging, the nginx front door, the per-instance admin generation, the secure-by-default configuration, this deployment guide, and 24/7 support with a guaranteed 24-hour response SLA. Slink is an independent open source project, and this image is not affiliated with or endorsed by it.
Deploy on Azure
Launch Slink on Ubuntu 24.04 LTS by SecureAnyCloud from the Azure Marketplace and follow this guide to a working self-hosted image sharing server in minutes.
Deploy on Azure
Launch Slink on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com