Deployment
Running the self-hosted server: HTTPS on your own network or behind a reverse proxy, systemd services, and security hardening.
Some features on this page require the Full Version.
Before You Begin
The self-hosted server is part of the Server tier. To run it you need a Server license key, which is issued automatically by email when you buy the Server tier.
The container will not start without two environment variables: ACCEPT_EULA=true and LICENSE_KEY set to your Server key. Both are included in the Docker Compose example below.
Ways to Serve It
Needlework Studio serves HTTPS out of the box. Where the certificate comes from depends on who uses the server.
- Only your own computers. The container makes its own certificate. No proxy, no domain name, nothing to renew. The pairing code is what confirms it. This is the default: leave
HTTPSunset. - Other people, in a browser. They need a certificate their browser already trusts, or they get a security warning. A reverse proxy gets one and keeps it renewed. Set
HTTPS=truewhen you use one. - Plain HTTP, nothing encrypting. Set
SELF_TLS=false. Only sensible on a network you already trust end to end, and it is how the server behaved before version 0.18.0.
If you are not sure, the first one is simpler and is what most home servers want.
http:// to https://, and the old one stops loading. Open the same address with https instead. To keep it exactly as it was, set SELF_TLS=false. Servers already behind a reverse proxy with HTTPS=true are unaffected.
On Your Own Network
Leave HTTPS unset and the container generates its own certificate the first time it starts, then serves HTTPS on port 6850. The certificate is kept in the data volume, so it survives restarts and updates.
A docker-compose.yml for this is short:
services:
needlework-studio:
image: ghcr.io/greenglasst/needleworkstudio:latest
ports:
- "6850:6850"
volumes:
- needlework-data:/data
environment:
- ACCEPT_EULA=true
- LICENSE_KEY=your-server-license-key
restart: unless-stopped
volumes:
needlework-data:
Your desktop computers then connect to https://your-server-ip:6850.
The Pairing Code
The pairing code is how a desktop confirms it reached your server and not something else answering on the same address. You enter it once, when you connect. If it does not match, nothing is sent.
Same code in three places:
- The container's startup output, boxed.
docker compose logs needlework-studio - The server's sign-in page, in a browser.
- The server's Settings page, once signed in.
The browser is easiest. On a network you share with people you do not know, use the logs instead. See Desktop Sync for the desktop side.
The first time you open the server in a browser you will see a certificate warning, because the certificate the container made was not issued by a certificate authority. That is expected for a server on your own network, and you can continue past it. The pairing code, not the browser, is what confirms your computer is talking to your own server.
Behind a Reverse Proxy
Use a reverse proxy when people other than you will open the server in a browser. It gives you a certificate browsers trust, renews it for you, and lets you serve on standard ports (80/443) alongside other services.
Set the HTTPS=true environment variable when you do. It tells the container a proxy is handling encryption, so it serves plain HTTP internally, skips making its own certificate, and marks session cookies as Secure. There is no pairing code in this setup, because the proxy's certificate already proves which server you reached.
Caddy (Recommended)
Caddy is a modern web server that automatically provisions and renews Let's Encrypt certificates. It requires minimal configuration and is the simplest way to add HTTPS to your deployment.
Create a docker-compose.yml that runs both Caddy and Needlework Studio:
services:
caddy:
image: caddy:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
restart: unless-stopped
needlework-studio:
image: ghcr.io/greenglasst/needleworkstudio:latest
volumes:
- needlework-data:/data
environment:
- HTTPS=true
- ACCEPT_EULA=true
- LICENSE_KEY=your-server-license-key
restart: unless-stopped
volumes:
needlework-data:
caddy-data:
caddy-config:
Replace your-server-license-key with the Server license key from your purchase email.
Create a Caddyfile in the same directory:
needlework.example.com {
reverse_proxy needlework-studio:6850
}
Replace needlework.example.com with your actual domain name. Caddy will automatically obtain an SSL certificate from Let's Encrypt and handle renewals. No additional port mapping is needed for the Needlework Studio container because Caddy communicates with it over the internal Docker network.
docker compose exec needlework-studio python manage_users.py admin, or add - ADMIN_USERNAME=your_username to the environment section of the needlework-studio service in your docker-compose.yml.
Other Reverse Proxies
Needlework Studio works with any reverse proxy (Nginx, Apache, Traefik, etc.). The key requirements are:
- Proxy all traffic to the application on port
6850. - Forward the
Host,X-Forwarded-For, andX-Forwarded-Protoheaders. - Set
HTTPS=trueon the Needlework Studio container when terminating SSL at the proxy.
Systemd Service
For manual (non-Docker) installations on Linux, you can run Needlework Studio as a systemd service so it starts automatically on boot and restarts on failure.
Create a unit file at /etc/systemd/system/needlework-studio.service:
[Unit]
Description=Needlework Studio
After=network.target
[Service]
Type=simple
User=needlework
Group=needlework
WorkingDirectory=/opt/needlework-studio
ExecStart=/opt/needlework-studio/venv/bin/gunicorn -w 1 --threads 4 -b 0.0.0.0:6850 app:app
Restart=on-failure
RestartSec=5
Environment=HTTPS=true
Environment=NEEDLEWORK_DATA_DIR=/opt/needlework-studio/data
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable needlework-studio
sudo systemctl start needlework-studio
Check that the service is running:
sudo systemctl status needlework-studio
View application logs:
sudo journalctl -u needlework-studio -f
Docker with Auto-Restart
The Docker Compose examples in this documentation include restart: unless-stopped, which ensures the container automatically restarts after a crash or a host reboot. This is the simplest way to achieve high availability with Docker.
The container will restart in all of the following scenarios:
- The application process crashes or exits with a non-zero code.
- The Docker daemon restarts.
- The host machine reboots.
The only time the container will not restart is if you explicitly stop it with docker compose down or docker stop. If you want the container to restart even after an explicit stop, use restart: always instead.
Security Notes
Needlework Studio includes several built-in security measures. Understanding these will help you configure your deployment securely.
CSRF Protection
All state-changing requests are protected by CSRF tokens. These tokens are automatically included in forms rendered by the application and validated on the server for every POST, PUT, and DELETE request.
Password Hashing
User passwords are hashed using Argon2, the winner of the Password Hashing Competition. Argon2 is resistant to GPU-based and ASIC-based brute-force attacks and is considered the current best practice for password storage.
Rate Limiting
The application enforces rate limits to prevent abuse:
- Login attempts: 5 requests per minute per IP address. This mitigates brute-force password attacks.
- Thread inventory updates: 60 requests per minute per user. This prevents accidental or automated flooding of the API.
Rate limiting is implemented in-memory, which is why a single Gunicorn worker (-w 1) is required. See Installation > Production with Gunicorn for details.
HTTP Security Headers
Needlework Studio sets the following security headers on all responses:
- Content-Security-Policy (CSP): Restricts the sources from which scripts, styles, images, and other resources can be loaded, mitigating cross-site scripting (XSS) attacks.
- Strict-Transport-Security (HSTS): Instructs browsers to only access the application over HTTPS, preventing protocol downgrade attacks.
- X-Frame-Options: Prevents the application from being embedded in iframes on other sites, protecting against clickjacking attacks.
Secure Cookies
When HTTPS=true is set, all session cookies are marked with the Secure flag, ensuring they are never transmitted over unencrypted HTTP connections. Cookies also use the HttpOnly and SameSite attributes to prevent JavaScript access and cross-site request attachment.
Upload Limits
To protect against denial-of-service through large file uploads:
- File size limit: 25 MB maximum per upload. Requests exceeding this size are rejected before the file is saved to disk.
- Decompression bomb protection: Image processing is capped at 25 megapixels. This prevents malicious images with extreme dimensions from consuming excessive memory during processing. Note that some modern cameras and phones exceed this in a single shot, so a very large photo may need downsizing before it will convert.
Updating
Your data is preserved across updates regardless of the installation method.
Docker
Pull the latest image and recreate the container:
docker compose pull && docker compose up -d
Manual Install
Pull the latest code and install any new dependencies:
git pull && pip install -r requirements.txt
Alternatively, download a fresh copy from the Downloads page and replace the application files, keeping your data directory intact.
Desktop App
Download the latest installer from the Downloads page and install it over the existing version. Your data is stored separately from the application and will not be affected.